fisk 2.3.0 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69f19e819ce868069b32edffc8cadcbd3d37a4253fb0686e2533a8cbea7010d5
4
- data.tar.gz: 1ff93558aa1c0d28c14a312e395ae0b8b675a8d0b11c650cc7cf6ee57bc52257
3
+ metadata.gz: eff395693048ed439f385f46b091872a7080058a68f9f859db2d8aedf4125573
4
+ data.tar.gz: a2070197036bb1a45b8451529e01cd17b7dc0f5ec1c4ee15f996c2079085cc8e
5
5
  SHA512:
6
- metadata.gz: b5396abb6b83733b3bb6022f615ed51f6fe8120d557b0162e913dd7e958d01b5864db51f7ac41145e362a711eee25d38915882059dd206e161ca4e3590cf6420
7
- data.tar.gz: d51e976bbc92c2682fe5287fc632618bb031805b0b1ace07f1a71481cc2cd2ae5f69c45fcab83821799cc0939f97b11761d237cfca7c3f6b360df82e1e14aa8f
6
+ metadata.gz: 28f8284e846de0ca14f2fd2a991be01ea79c0e72f96fbfc3b8a6b0c5d6561892ed2046c9cff4391a3cf2b8f38a225b89f8c89046fb9a06a38813e56f7db4825b
7
+ data.tar.gz: f6f9a5b64ace9e6edc805a63115ead36ea7d19c0c86f809593f9418f524559b044b77b727055cf206c066708cfdc33b453a137ff0416d75abc84bfaf36f969a4
@@ -29,7 +29,6 @@ jobs:
29
29
  uses: ruby/setup-ruby@477b21f02be01bcb8030d50f37cfec92bfa615b6
30
30
  with:
31
31
  ruby-version: 3.0
32
- - name: Install dependencies
33
- run: bundle install
32
+ bundler-cache: true # 'bundle install' and cache
34
33
  - name: Run tests
35
34
  run: bundle exec rake
data/README.md CHANGED
@@ -171,3 +171,8 @@ Target 0: (ruby) stopped.
171
171
  frame #32: 0x00007fff20530621 libdyld.dylib`start + 1
172
172
  frame #33: 0x00007fff20530621 libdyld.dylib`start + 1
173
173
  ```
174
+
175
+ Note that in order to produce a stack trace like the above, the Ruby binary must include the debugging symbols (otherwise, the `ruby` frames will not be displayed); this is accomplished by specifying the `--ggdb3` compiler flag:
176
+
177
+ - if compiling Ruby from source, use `./configure optflags=-gddb3` in the build process
178
+ - if using a version manager, refer to the help; example for RVM: `optflags="-ggdb3" rvm install 3.0.2 --disable-binary`
@@ -22,7 +22,16 @@ class Fisk
22
22
  end
23
23
 
24
24
  Form = Struct.new(:operands, :encodings)
25
- Instruction = Struct.new(:name, :forms)
25
+ Instruction = Struct.new(:name, :forms) do
26
+ def check_performance(operands)
27
+ case name
28
+ when Instructions::MOV.name
29
+ if operands[0].register? && operands[1].register? && operands[0].name == operands[1].name
30
+ return "MOV with same register (#{operands[0].name})"
31
+ end
32
+ end
33
+ end
34
+ end
26
35
 
27
36
  OPERAND_TYPES = [
28
37
  <%-
data/lib/fisk/errors.rb CHANGED
@@ -3,6 +3,10 @@ class Fisk
3
3
  class Error < StandardError
4
4
  end
5
5
 
6
+ # Raised when an instruction isn't found or can't be generated
7
+ class InvalidInstructionError < Error
8
+ end
9
+
6
10
  # Raised when a temporary register is released more than once
7
11
  class AlreadyReleasedError < Error
8
12
  end
@@ -31,5 +35,16 @@ class Fisk
31
35
  # `put_label` with the same name twice
32
36
  class LabelAlreadyDefined < Error
33
37
  end
38
+
39
+ # Raised when the performance check is enabled, and suboptimal instructions
40
+ # are found.
41
+ class SuboptimalPerformance < Error
42
+ attr_reader :warnings
43
+
44
+ def initialize(warnings)
45
+ super "Suboptimal instructions found!"
46
+ @warnings = warnings
47
+ end
48
+ end
34
49
  end
35
50
  end
@@ -36,7 +36,16 @@ class Fisk
36
36
  end
37
37
 
38
38
  Form = Struct.new(:operands, :encodings)
39
- Instruction = Struct.new(:name, :forms)
39
+ Instruction = Struct.new(:name, :forms) do
40
+ def check_performance(operands)
41
+ case name
42
+ when Instructions::MOV.name
43
+ if operands[0].register? && operands[1].register? && operands[0].name == operands[1].name
44
+ return "MOV with same register (#{operands[0].name})"
45
+ end
46
+ end
47
+ end
48
+ end
40
49
 
41
50
  OPERAND_TYPES = [
42
51
  Operand.new("al", true, true),
data/lib/fisk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Fisk
2
- VERSION = "2.3.0"
2
+ VERSION = "2.3.2"
3
3
  end
data/lib/fisk.rb CHANGED
@@ -20,6 +20,22 @@ class Fisk
20
20
  def absolute_location?; false; end
21
21
  end
22
22
 
23
+ module OperandSize
24
+ def compute_size type
25
+ return 128 if type.start_with?('xmm')
26
+
27
+ bits = type[/^r(\d+)$/, 1]&.to_i
28
+
29
+ if bits.nil?
30
+ raise ArgumentError, "Unexpected register type (#{type}); 'r<bits>' expected"
31
+ elsif bits % 8 != 0
32
+ raise ArgumentError, "Unexpected register size (#{bits}); multiple of 8 expected"
33
+ else
34
+ bits
35
+ end
36
+ end
37
+ end
38
+
23
39
  class Operand
24
40
  include OperandPredicates
25
41
 
@@ -47,12 +63,15 @@ class Fisk
47
63
 
48
64
  module Registers
49
65
  class Register < Operand
50
- attr_reader :name, :type, :value
66
+ include OperandSize
67
+
68
+ attr_reader :name, :type, :value, :size
51
69
 
52
70
  def initialize name, type, value
53
71
  @name = name
54
72
  @type = type
55
73
  @value = value
74
+ @size = compute_size(type)
56
75
  end
57
76
 
58
77
  def works? op
@@ -70,6 +89,10 @@ class Fisk
70
89
 
71
90
  def register?; true; end
72
91
 
92
+ def integer?; false; end
93
+
94
+ def to_i; @value; end
95
+
73
96
  def to_register
74
97
  self
75
98
  end
@@ -104,7 +127,9 @@ class Fisk
104
127
  end
105
128
 
106
129
  class Temp < Operand
107
- attr_reader :name, :type
130
+ include OperandSize
131
+
132
+ attr_reader :name, :type, :size
108
133
  attr_accessor :register, :start_point, :end_point
109
134
 
110
135
  def initialize name, type
@@ -112,6 +137,7 @@ class Fisk
112
137
  @type = type
113
138
  @start_point = nil
114
139
  @end_point = nil
140
+ @size = compute_size(type)
115
141
  end
116
142
 
117
143
  def temp_register?; true; end
@@ -508,9 +534,15 @@ class Fisk
508
534
  end
509
535
  end
510
536
 
511
- def initialize
537
+ # params:
538
+ # :check_performance: Raise a SuboptimalPerformance error on write if suboptimal
539
+ # instructions are detected.
540
+ #
541
+ def initialize check_performance: false
512
542
  @instructions = []
513
543
  @labels = {}
544
+ @check_performance = check_performance
545
+ @performance_warnings = []
514
546
  # A set of temp registers recorded as we see them (not at allocation time)
515
547
  @temp_registers = Set.new
516
548
  yield self if block_given?
@@ -763,6 +795,8 @@ class Fisk
763
795
 
764
796
  # Encode all instructions and write them to +buffer+. +buffer+ should be an
765
797
  # IO object.
798
+ # If the performance check is enabled, a runtime error is raised if suboptimal
799
+ # instructions are found.
766
800
  def write_to buffer, metadata: {}
767
801
  labels = {}
768
802
  comments = {}
@@ -777,7 +811,7 @@ class Fisk
777
811
  elsif insn.comment?
778
812
  comments.update({buffer.pos => insn.message}) { |_, *lines| lines.join($/) }
779
813
  elsif insn.lazy?
780
- insn.encode buffer, labels
814
+ write_instruction insn, buffer, labels
781
815
  instructions.unshift(*@instructions)
782
816
  @instructions.clear
783
817
  else
@@ -793,19 +827,24 @@ class Fisk
793
827
  metadata[:comments] = comments
794
828
  @instructions = backup
795
829
 
796
- return if unresolved.empty?
830
+ if !unresolved.empty?
831
+ pos = buffer.pos
832
+ unresolved.each do |req|
833
+ insn = req.insn
834
+ buffer.seek req.io_seek_pos, IO::SEEK_SET
835
+ write_instruction insn, buffer, labels
836
+ end
837
+ buffer.seek pos, IO::SEEK_SET
838
+ end
797
839
 
798
- pos = buffer.pos
799
- unresolved.each do |req|
800
- insn = req.insn
801
- buffer.seek req.io_seek_pos, IO::SEEK_SET
802
- insn.encode buffer, labels
840
+ if !@performance_warnings.empty?
841
+ raise Errors::SuboptimalPerformance.new(@performance_warnings)
803
842
  end
804
- buffer.seek pos, IO::SEEK_SET
805
843
 
806
844
  buffer
807
845
  end
808
846
 
847
+ # If the performance check is enabled, warnings are added to @performance_warnings.
809
848
  def gen_with_insn insns, params
810
849
  forms = insns.forms.find_all do |insn|
811
850
  if insn.operands.length == params.length
@@ -826,7 +865,7 @@ class Fisk
826
865
  Valid forms:
827
866
  #{valid_forms}
828
867
  eostr
829
- raise NotImplementedError, msg
868
+ raise Errors::InvalidInstructionError, msg
830
869
  end
831
870
 
832
871
  form = forms.first
@@ -857,7 +896,14 @@ class Fisk
857
896
  end
858
897
  end
859
898
 
860
- insn ||= Instruction.new(insns, form, params)
899
+ if insn.nil?
900
+ insn = Instruction.new(insns, form, params)
901
+
902
+ if @check_performance
903
+ warning = insns.check_performance(params)
904
+ @performance_warnings << warning if warning
905
+ end
906
+ end
861
907
 
862
908
  @instructions << insn
863
909
 
data/test/test_fisk.rb CHANGED
@@ -12,6 +12,10 @@ class FiskTest < Fisk::Test
12
12
  assert_predicate Fisk::Registers::RDI, :register?
13
13
  end
14
14
 
15
+ def test_rax_is_not_an_integer
16
+ refute_predicate Fisk::Registers::RAX, :integer?
17
+ end
18
+
15
19
  def test_push_all
16
20
  regs = Fisk::Registers.constants.grep(/^R[A-Z0-9]{1,2}/).find_all { |r|
17
21
  Fisk::Registers.const_get(r).type == "r64"
@@ -163,6 +167,12 @@ class FiskTest < Fisk::Test
163
167
  assert_equal sprintf("%#02x", expected_pos), i.op_str.to_s
164
168
  end
165
169
 
170
+ def test_invalid_instruction
171
+ assert_raises(Fisk::Errors::InvalidInstructionError) do
172
+ fisk.mov(fisk.imm(5), fisk.imm(5))
173
+ end
174
+ end
175
+
166
176
  %w{ rax rcx rdx rbx rsp rbp rsi rdi r8 r9 r10 }.each do |reg|
167
177
  define_method "test_#{reg}_to_offset" do
168
178
  fisk.lea(fisk.send(reg), fisk.rip(15))
@@ -9,7 +9,12 @@ class Fisk
9
9
  def write_instruction insn, buffer, labels
10
10
  pos = buffer.pos
11
11
  x = insn.encode buffer, labels
12
- raise unless x == buffer.pos - pos
12
+
13
+ if insn.lazy?
14
+ raise unless buffer.pos = pos
15
+ else
16
+ raise unless x == buffer.pos - pos
17
+ end
13
18
  end
14
19
  end
15
20
 
@@ -0,0 +1,32 @@
1
+ require "helper"
2
+
3
+ class Fisk
4
+ class PerformanceCheckTest < Fisk::Test
5
+ include Registers
6
+
7
+ attr_reader :fisk
8
+
9
+ def setup
10
+ super
11
+ @fisk = Fisk.new check_performance: true
12
+ end
13
+
14
+ def test_mov_same_register
15
+ buf = StringIO.new(''.b)
16
+
17
+ fisk.mov R9, R9
18
+ fisk.mov RAX, RAX
19
+
20
+ error = assert_raises Errors::SuboptimalPerformance do
21
+ fisk.write_to buf
22
+ end
23
+
24
+ expected_warnings = [
25
+ "MOV with same register (r9)",
26
+ "MOV with same register (rax)",
27
+ ]
28
+
29
+ assert_equal error.warnings, expected_warnings
30
+ end
31
+ end
32
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fisk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Patterson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-25 00:00:00.000000000 Z
11
+ date: 2023-04-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -1321,6 +1321,7 @@ files:
1321
1321
  - test/test_cfg.rb
1322
1322
  - test/test_fisk.rb
1323
1323
  - test/test_fisk_written_size.rb
1324
+ - test/test_performance_check.rb
1324
1325
  - test/test_register_allocation.rb
1325
1326
  - test/test_run_fisk.rb
1326
1327
  homepage: https://github.com/tenderlove/fisk
@@ -1342,7 +1343,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1342
1343
  - !ruby/object:Gem::Version
1343
1344
  version: '0'
1344
1345
  requirements: []
1345
- rubygems_version: 3.3.0.dev
1346
+ rubygems_version: 3.5.0.dev
1346
1347
  signing_key:
1347
1348
  specification_version: 4
1348
1349
  summary: Write assembly in Ruby!
@@ -1351,5 +1352,6 @@ test_files:
1351
1352
  - test/test_cfg.rb
1352
1353
  - test/test_fisk.rb
1353
1354
  - test/test_fisk_written_size.rb
1355
+ - test/test_performance_check.rb
1354
1356
  - test/test_register_allocation.rb
1355
1357
  - test/test_run_fisk.rb