fisk 2.3.0 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.
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