voodoo 0.5.0

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.
@@ -0,0 +1,55 @@
1
+ require 'voodoo/config'
2
+ require 'voodoo/generators/command_postprocessor'
3
+
4
+ module Voodoo
5
+ # Class that generates ELF object files by invoking nasm on
6
+ # the output of a code generator that generates NASM assembly.
7
+ class NasmELFGenerator
8
+ def initialize nasmgenerator, nasm_extra_args
9
+ @nasmgenerator = nasmgenerator
10
+ @nasm_extra_args = nasm_extra_args
11
+ end
12
+
13
+ include CommandPostProcessor
14
+
15
+ def output_file_name input_name
16
+ input_name.sub(/\.voo$/, '') + '.o'
17
+ end
18
+
19
+ # Writes the generated code to the given IO handle
20
+ def write io
21
+ # Create temporary file to write NASM code to
22
+ if io.respond_to? :path
23
+ base = File.basename(io.path).sub(/([^.]*).*/, "\\1")
24
+ else
25
+ base = self.class.name
26
+ end
27
+
28
+ Tempfile.open(base + '.asm') do |nasmfile|
29
+ Tempfile.open(base + '.o') do |elffile|
30
+ elffile.close
31
+ # Write NASM code to nsamfile
32
+ @nasmgenerator.write nasmfile
33
+ nasmfile.close
34
+ # Find out the name of the nasm executable
35
+ if ENV.has_key? 'NASM'
36
+ nasm = ENV['NASM']
37
+ elsif ENV.has_key? 'YASM'
38
+ nasm = ENV['YASM']
39
+ else
40
+ nasm = Voodoo::Config.nasm_command
41
+ end
42
+ # Invoke nasm on nasmfile
43
+ command = "#{nasm} #{@nasm_extra_args}" +
44
+ " -o #{shell_encode elffile.path}" +
45
+ " #{shell_encode nasmfile.path}"
46
+ if system(command)
47
+ write_file_to_io elffile.path, io
48
+ end
49
+ elffile.unlink
50
+ end
51
+ nasmfile.unlink
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,679 @@
1
+ require 'voodoo/generators/common_code_generator'
2
+
3
+ module Voodoo
4
+ # = NASM Code Generator
5
+ #
6
+ # The NASM code generator is a common base class for generators that output
7
+ # assembly code for use with the {Netwide Assembler}[http://www.nasm.us/].
8
+ #
9
+ # This class is used by both the I386NasmGenerator and the
10
+ # AMD64NasmGenerator, and contains the functionality that is common to
11
+ # both.
12
+ #
13
+ # To use the functionality from this class, a subclass must define the
14
+ # following methods and constants:
15
+ # - #emit_function_prologue
16
+ # - #load_arg
17
+ # - #load_local
18
+ # - @CODE_ALIGNMENT
19
+ # - @DATA_ALIGNMENT
20
+ # - @FUNCTION_ALIGNMENT
21
+ # - @SCRATCH_REG
22
+ # - @WORD_NAME
23
+ # - @WORDSIZE
24
+ # - @AX, @BX, @CX, and @DX
25
+ class NasmGenerator < CommonCodeGenerator
26
+ def initialize params = {}
27
+ super params
28
+ @if_labels = []
29
+ end
30
+
31
+ #
32
+ # == Information About the Generator
33
+ #
34
+
35
+ # Given an input file name, returns the canonical output file name
36
+ # for this code generator.
37
+ def output_file_name input_name
38
+ input_name.sub(/\.voo$/, '') + '.asm'
39
+ end
40
+
41
+ # Returns the number of bits per word for this code generator.
42
+ def wordsize
43
+ @WORDSIZE * 8
44
+ end
45
+
46
+ #
47
+ # == Labels
48
+ #
49
+
50
+ # Export symbols from the current section
51
+ def export *symbols
52
+ emit "global #{symbols.join ', '}\n"
53
+ end
54
+
55
+ # Continue execution at the given address
56
+ def goto value
57
+ emit "; goto #{value}\n"
58
+ value_ref = load_value value, @SCRATCH_REG
59
+ emit "jmp #{value_ref}\n"
60
+ end
61
+
62
+ # Import labels into the current section
63
+ def import *symbols
64
+ emit "extern #{symbols.join ', '}\n"
65
+ end
66
+
67
+ # Define a label in the current section
68
+ def label name
69
+ emit "#{name}:\n"
70
+ end
71
+
72
+ #
73
+ # == Data definition
74
+ #
75
+
76
+ # Define a byte with the given value
77
+ def byte value
78
+ emit "db #{value}\n"
79
+ end
80
+
81
+ # Define a dword with the given value
82
+ def dword value
83
+ emit "dd #{value}\n"
84
+ end
85
+
86
+ # Define a qword with the given value
87
+ def qword value
88
+ emit "dq #{value}\n"
89
+ end
90
+
91
+ # Define a string with the given value
92
+ def string value
93
+ code = ''
94
+ in_quote = false
95
+ value.each_byte do |b|
96
+ if b >= 32 && b < 128
97
+ if in_quote
98
+ code << b.chr
99
+ else
100
+ code << ',' unless code.empty?
101
+ code << "'" + b.chr
102
+ in_quote = true
103
+ end
104
+ else
105
+ if in_quote
106
+ code << "',#{b}"
107
+ in_quote = false
108
+ else
109
+ code << ',' unless code.empty?
110
+ code << "#{b}"
111
+ end
112
+ end
113
+ end
114
+ code << "'" if in_quote
115
+ emit "db #{code}\n"
116
+ end
117
+
118
+ #
119
+ # == Alignment
120
+ #
121
+
122
+ def align alignment = nil
123
+ unless alignment
124
+ # Get default alignment
125
+ case @section
126
+ when :code
127
+ alignment = @CODE_ALIGNMENT
128
+ when :data
129
+ alignment = @DATA_ALIGNMENT
130
+ when :function
131
+ alignment = @FUNCTION_ALIGNMENT
132
+ else
133
+ # Use data alignment as default
134
+ alignment = @DATA_ALIGNMENT
135
+ end
136
+ end
137
+ emit "align #{alignment}\n" unless alignment == 0
138
+ end
139
+
140
+ #
141
+ # == Functions
142
+ #
143
+
144
+ # Emit function preamble and declare _formals_ as function arguments
145
+ def begin_function *formals
146
+ emit "; function #{formals.join ' '}\n"
147
+ environment = Environment.new @environment
148
+ environment.add_args formals
149
+ @environment = environment
150
+ emit_function_prologue formals
151
+ end
152
+
153
+ # Emit function epilogue.
154
+ def emit_function_epilogue formals = []
155
+ emit "leave\n"
156
+ end
157
+
158
+ # End a function body
159
+ def end_function
160
+ emit "; end function\n\n"
161
+ if @environment == @top_level
162
+ raise "Cannot end function when not in a function"
163
+ else
164
+ @environment = @top_level
165
+ end
166
+ end
167
+
168
+ # Return a from a function.
169
+ #
170
+ # _words_ may contain an expression to be evaluated. The result
171
+ # of the evaluation is returned from the function.
172
+ def ret *words
173
+ emit "; return #{words.join ' '}\n"
174
+ eval_expr words
175
+ emit_function_epilogue
176
+ emit "ret\n"
177
+ end
178
+
179
+ #
180
+ # == Conditionals
181
+ #
182
+
183
+ # End a conditional body
184
+ def end_if
185
+ label = @if_labels.pop
186
+ emit "#{label}:\n"
187
+ end
188
+
189
+ #
190
+ # == Value Classification
191
+ #
192
+
193
+ # Test if op is a binary operation
194
+ def binop? op
195
+ [:div, :mod, :sub].member?(op) || symmetric_operation?(op)
196
+ end
197
+
198
+ # Test if a value is an integer
199
+ def integer? value
200
+ value.kind_of? Integer
201
+ end
202
+
203
+ # Test if a symbol refers to a global
204
+ def global? symbol
205
+ symbol?(symbol) && @environment[symbol] == nil
206
+ end
207
+
208
+ # Tests if an operand is an immediate operand
209
+ def immediate_operand? operand
210
+ integer?(operand) || global?(operand)
211
+ end
212
+
213
+ # Tests if an operand is a memory operand
214
+ def memory_operand? operand
215
+ operand[0] == ?[
216
+ end
217
+
218
+ # Test if a value is a symbol
219
+ def symbol? value
220
+ value.kind_of? Symbol
221
+ end
222
+
223
+ # Test if op is a symmetric operation (i.e. it will yield the
224
+ # same result if the order of its source operands is changed).
225
+ def symmetric_operation? op
226
+ [:add, :and, :mul, :or, :xor].member? op
227
+ end
228
+
229
+ #
230
+ # == Loading Values
231
+ #
232
+
233
+ # Create a value reference to an address.
234
+ # Invoking this code may clobber @BX and/or @CX
235
+ def load_address base, offset, scale
236
+ base_ref = load_value base, @BX
237
+ offset_ref = load_value offset, @CX
238
+
239
+ if offset_ref == 0
240
+ if integer? base_ref
241
+ # Only an integer base
242
+ "[#{base_ref}]"
243
+ else
244
+ # Some complex base; load in @BX
245
+ emit "mov #{@BX}, #{base_ref}\n"
246
+ "[#{@BX}]"
247
+ end
248
+ elsif base_ref == 0
249
+ if integer? offset_ref
250
+ # Only a scaled offset
251
+ "[#{offset_ref.to_i * scale}]"
252
+ else
253
+ # Some complex offset; load in @CX
254
+ emit "mov #{@CX}, #{offset_ref}\n"
255
+ "[#{@CX} * #{scale}]"
256
+ end
257
+ elsif integer? base_ref
258
+ if integer? offset_ref
259
+ # All integers, combine them together
260
+ "[#{base_ref.to_i + (offset_ref.to_i * scale)}]"
261
+ else
262
+ # Complex offset; use @CX
263
+ emit "mov #{@CX}, #{offset_ref}\n"
264
+ "[#{base_ref} + #{@CX} * #{scale}]"
265
+ end
266
+ elsif integer? offset_ref
267
+ # Complex base, integer offset; use @BX
268
+ emit "mov #{@BX}, #{base_ref}\n"
269
+ "[#{@BX} + #{offset_ref.to_i * scale}]"
270
+ else
271
+ # Both base and offset are complex
272
+ # Use both @BX and @CX
273
+ emit "mov #{@BX}, #{base_ref}\n"
274
+ emit "mov #{@CX}, #{offset_ref}\n"
275
+ "[#{@BX} + #{@CX} * #{scale}]"
276
+ end
277
+ end
278
+
279
+ # Load the value associated with the given symbol.
280
+ # Returns a string that can be used to refer to the loaded value.
281
+ def load_symbol symbol, reg = @SCRATCH_REG
282
+ x = @environment[symbol]
283
+ if x
284
+ case x[0]
285
+ when :arg
286
+ load_arg x[1], reg
287
+ when :local
288
+ load_local x[1], reg
289
+ else
290
+ raise "Invalid variable type: #{x[0]}"
291
+ end
292
+ else
293
+ # Assume global
294
+ symbol.to_s
295
+ end
296
+ end
297
+
298
+ # Load a value.
299
+ # Returns a string that can be used to refer to the loaded value.
300
+ def load_value value, reg = @SCRATCH_REG
301
+ if integer? value
302
+ # Integers can be used as is
303
+ value
304
+ elsif symbol? value
305
+ load_symbol value, reg
306
+ end
307
+ end
308
+
309
+ #
310
+ # == Storing Values
311
+ #
312
+
313
+ # Evaluate the expr in words and store the result in target
314
+ def set target, *words
315
+ if integer? target
316
+ raise "Cannot change value of integer #{target}"
317
+ elsif global?(target)
318
+ raise "Cannot change value of global #{target}"
319
+ end
320
+
321
+ emit "; set #{target} #{words.join ' '}\n"
322
+ if words.length == 1
323
+ if words[0] == target
324
+ emit "; nothing to do; destination equals source\n"
325
+ else
326
+ target_ref = load_value target, @BX
327
+ if integer?(words[0])
328
+ if words[0].to_i == 0
329
+ # Set destination to 0
330
+ emit "xor #{@AX}, #{@AX}\n"
331
+ emit "mov #{target_ref}, #{@AX}\n"
332
+ else
333
+ # Load immediate
334
+ emit "mov #{@WORD_NAME} #{target_ref}, #{words[0]}\n"
335
+ end
336
+ else
337
+ # Copy source to destination
338
+ eval_expr words, @RETURN_REG
339
+ emit "mov #{target_ref}, #{@RETURN_REG}\n"
340
+ end
341
+ end
342
+ else
343
+ op = words[0]
344
+
345
+ if words.length == 3 && binop?(op)
346
+ # Binary operation
347
+ binop op, target, words[1], words[2]
348
+ else
349
+ # Not a binary operation
350
+ eval_expr words, @RETURN_REG
351
+ target_ref = load_value target, @BX
352
+ emit "mov #{target_ref}, #{@RETURN_REG}\n"
353
+ end
354
+ end
355
+ end
356
+
357
+ # Set the byte at _base_ + _offset_ to _value_
358
+ def set_byte base, offset, value
359
+ emit "; set-byte #{base} #{offset} #{value}\n"
360
+ value_ref = load_value value, @RETURN_REG
361
+ addr_ref = load_address base, offset, 1
362
+ emit "mov byte #{addr_ref}, #{value_ref}\n"
363
+ end
364
+
365
+ # Set the word at _base_ + _offset_ * +@WORDSIZE+ to _value_
366
+ def set_word base, offset, value
367
+ emit "; set-word #{base} #{offset} #{value}\n"
368
+ value_ref = load_value value, @RETURN_REG
369
+ addr_ref = load_address base, offset, @WORDSIZE
370
+ emit "mov dword #{addr_ref}, #{value_ref}\n"
371
+ end
372
+
373
+ #
374
+ # == Binary Operations
375
+ #
376
+
377
+ # Emit code for a binary operation
378
+ def binop op, target, x, y
379
+ if target == x
380
+ binop2 op, target, y
381
+ elsif symmetric_operation?(op) && y == target
382
+ binop2 op, target, x
383
+ else
384
+ # Cases that are handled specially
385
+ return div(target, x, y) if op == :div
386
+ return mod(target, x, y) if op == :mod
387
+ return mul(target, x, y) if op == :mul
388
+
389
+ target_ref = load_value target, @AX
390
+ x_ref = load_value x, @DX
391
+ y_ref = load_value y, @BX
392
+
393
+ if memory_operand?(target_ref)
394
+ if memory_operand?(x_ref) || memory_operand?(y_ref)
395
+ emit "mov #{@CX}, #{x_ref}\n"
396
+ emit "#{op} #{@CX}, #{y_ref}\n"
397
+ emit "mov #{target_ref}, #{@CX}\n"
398
+ else
399
+ emit "mov #{@WORD_NAME} #{target_ref}, #{x_ref}\n"
400
+ emit "#{op} #{@WORD_NAME} #{target_ref}, #{y_ref}\n"
401
+ end
402
+ else
403
+ raise "Can't happen: target_ref is #{target_ref.inspect}"
404
+ end
405
+ end
406
+ end
407
+
408
+ # Emit code for a binary operation where the first operand
409
+ # is also the target
410
+ def binop2 op, target, y
411
+ # Cases that are handled specially
412
+ return div2(target, target, y) if op == :div
413
+ return mod2(target, y) if op == :mod
414
+ return mul2(target, y) if op == :mul
415
+
416
+ target_ref = load_value target, @BX
417
+ y_ref = load_value y, @DX
418
+ if memory_operand?(target_ref) && memory_operand?(y_ref)
419
+ emit "mov #{@AX}, #{y_ref}\n"
420
+ emit "#{op} #{target_ref}, #{@AX}\n"
421
+ else
422
+ emit "#{op} #{@WORD_NAME} #{target_ref}, #{y_ref}\n"
423
+ end
424
+ end
425
+
426
+ # Divide x by y and store the quotient in target
427
+ def div target, x, y
428
+ eval_div x, y
429
+ target_ref = load_value target, @BX
430
+ emit "mov #{target_ref}, #{@AX}\n"
431
+ end
432
+
433
+ # Divide target by x and store the quotient in target
434
+ def div2 target, x
435
+ div target, target, x
436
+ end
437
+
438
+ # Divide x by y and store the remainder in target
439
+ def mod target, x, y
440
+ eval_div x, y
441
+ target_ref = load_value target, @BX
442
+ emit "mov #{target_ref}, #{@DX}\n"
443
+ end
444
+
445
+ # Divide target by x and store the remainder in target
446
+ def mod2 target, x
447
+ mod target, target, x
448
+ end
449
+
450
+ # Multiply x by y and store the result in target
451
+ def mul target, x, y
452
+ eval_mul x, y
453
+ target_ref = load_value target, @BX
454
+ emit "mov #{target_ref}, #{@RETURN_REG}\n"
455
+ end
456
+
457
+ # Multiply target by x and store the result in target
458
+ def mul2 target, x
459
+ mul target, target, x
460
+ end
461
+
462
+ #
463
+ # == Expressions
464
+ #
465
+
466
+ # Perform division.
467
+ # The quotient is stored in @AX, the remainder in @DX.
468
+ def eval_div x, y
469
+ x_ref = load_value_into_register x, @AX
470
+ y_ref = load_value y, @SCRATCH_REG
471
+ set_register @DX, 0
472
+ if immediate_operand?(y_ref)
473
+ set_register @BX, y_ref
474
+ emit "idiv #{@BX}\n"
475
+ else
476
+ emit "idiv #{@WORD_NAME} #{y_ref}\n"
477
+ end
478
+ end
479
+
480
+ # Evaluate an expression.
481
+ # The result is stored in _register_ (@RETURN_REG by default).
482
+ def eval_expr words, register = @RETURN_REG
483
+ if words.length == 1
484
+ if words[0] == 0
485
+ emit "xor #{register}, #{register}\n"
486
+ else
487
+ load_value_into_register words[0], register
488
+ end
489
+ else
490
+ op = words[0]
491
+ case op
492
+ when :call
493
+ call *words[1..-1]
494
+ when :div
495
+ eval_div words[1], words[2]
496
+ set_register register, @AX
497
+ when :'get-byte'
498
+ # Clear register
499
+ set_register register, 0
500
+ # Get address reference
501
+ address_ref = load_address words[1], words[2], 1
502
+ # Load byte from address
503
+ case register
504
+ when 'eax', 'rax'
505
+ set_register 'al', address_ref
506
+ when 'ebx', 'rbx'
507
+ set_register 'bl', address_ref
508
+ when 'ecx', 'rcx'
509
+ set_register 'cl', address_ref
510
+ when 'edx', 'rdx'
511
+ set_register 'dl', address_ref
512
+ else
513
+ set_register @BX, 0
514
+ set_register 'bl', address_ref
515
+ set_register register, @BX
516
+ end
517
+ when :'get-word'
518
+ address_ref = load_address words[1], words[2], @WORDSIZE
519
+ set_register register, address_ref
520
+ when :mod
521
+ eval_div words[1], words[2]
522
+ set_register register, @DX
523
+ when :mul
524
+ eval_mul words[1], words[2], register
525
+ when :not
526
+ load_value_into_register words[1], register
527
+ emit "not #{register}\n"
528
+ else
529
+ if binop?(op)
530
+ x_ref = load_value words[1], @DX
531
+ y_ref = load_value words[2], @BX
532
+ emit "mov #{register}, #{x_ref}\n"
533
+ emit "#{op} #{register}, #{y_ref}\n"
534
+ else
535
+ raise "Not a magic word: #{words[0]}"
536
+ end
537
+ end
538
+ end
539
+ end
540
+
541
+ # Multiply x by y.
542
+ # The result is stored in @AX by default, but
543
+ # a different register can be specified by passing
544
+ # a third argument.
545
+ def eval_mul x, y, register = @AX
546
+ x_ref = load_value x, @DX
547
+ y_ref = load_value y, @BX
548
+
549
+ if immediate_operand? x_ref
550
+ if immediate_operand? y_ref
551
+ set_register register, x_ref * y_ref
552
+ else
553
+ emit "imul #{register}, #{y_ref}, #{x_ref}\n"
554
+ end
555
+ elsif immediate_operand? y_ref
556
+ emit "imul #{register}, #{x_ref}, #{y_ref}\n"
557
+ else
558
+ emit "mov #{register}, #{x_ref}\n"
559
+ emit "imul #{register}, #{y_ref}\n"
560
+ end
561
+ end
562
+
563
+ #
564
+ # == Conditionals
565
+ #
566
+
567
+ # Start a conditional using the specified branch instruction
568
+ # after the comparison.
569
+ def common_if branch, x, y = nil
570
+ load_value_into_register y, @DX if y
571
+ load_value_into_register x, @AX
572
+ truelabel = @environment.gensym
573
+ falselabel = @environment.gensym
574
+ @if_labels.push falselabel
575
+
576
+ emit "cmp #{@AX}, #{@DX}\n"
577
+ emit "#{branch} #{truelabel}\n"
578
+ emit "jmp #{falselabel}\n"
579
+ emit "#{truelabel}:\n"
580
+ end
581
+
582
+ # End a conditional.
583
+ def end_if
584
+ label = @if_labels.pop
585
+ emit "#{label}:\n"
586
+ end
587
+
588
+ # Start the false path of a conditional.
589
+ def ifelse
590
+ emit "; else\n"
591
+ newlabel = @environment.gensym
592
+ emit "jmp #{newlabel}\n"
593
+ label = @if_labels.pop
594
+ emit "#{label}:\n"
595
+ @if_labels.push newlabel
596
+ end
597
+
598
+ # Test if x is equal to y
599
+ def ifeq x, y
600
+ emit "; ifeq #{x} #{y}\n"
601
+ common_if 'je', x, y
602
+ end
603
+
604
+ # Test if x is greater than or equal to y
605
+ def ifge x, y
606
+ emit "; ifge #{x} #{y}\n"
607
+ common_if 'jge', x, y
608
+ end
609
+
610
+ # Test if x is strictly greater than y
611
+ def ifgt x, y
612
+ emit "; ifgt #{x} #{y}\n"
613
+ common_if 'jg', x, y
614
+ end
615
+
616
+ # Test if x is less than or equal to y
617
+ def ifle x, y
618
+ emit "; ifle #{x} #{y}\n"
619
+ common_if 'jle', x, y
620
+ end
621
+
622
+ # Test if x is strictly less than y
623
+ def iflt x, y
624
+ emit "; iflt #{x} #{y}\n"
625
+ common_if 'jl', x, y
626
+ end
627
+
628
+ # Test if x different from y
629
+ def ifne x, y
630
+ emit "; ifne #{x} #{y}\n"
631
+ common_if 'jne', x, y
632
+ end
633
+
634
+ #
635
+ # == Miscellaneous
636
+ #
637
+
638
+ # Emit a comment
639
+ def comment text
640
+ emit ";#{text}\n"
641
+ end
642
+
643
+ # Load a value into a register
644
+ def load_value_into_register value, register
645
+ value_ref = load_value value, register
646
+ set_register register, value_ref
647
+ end
648
+
649
+ # Set a register to a value.
650
+ # The value must be a valid operand to the mov instruction.
651
+ def set_register register, value_ref
652
+ case value_ref
653
+ when register
654
+ # Nothing to do
655
+ when 0
656
+ emit "xor #{register}, #{register}\n"
657
+ else
658
+ emit "mov #{register}, #{value_ref}\n"
659
+ end
660
+ end
661
+
662
+ #
663
+ # == Output
664
+ #
665
+
666
+ # Write generated code to the given IO object.
667
+ def write io
668
+ io.puts "bits #{@WORDSIZE * 8}\n\n"
669
+ @sections.each do |section,code|
670
+ unless code.empty?
671
+ io.puts "section #{section.to_s}"
672
+ io.puts code
673
+ io.puts
674
+ end
675
+ end
676
+ end
677
+
678
+ end
679
+ end