Class: Voodoo::ARMGasGenerator

Inherits:
CommonCodeGenerator show all
Defined in:
lib/voodoo/generators/arm_gas_generator.rb

Overview

ARM GNU Assembler Code Generator

The ARM code generator generates assembly code for use with the GNU assembler.

Calling Convention

The first four arguments are passed in the registers r0 through r3. Any additional arguments are passed on the stack, starting at r13. r13 will always be a multiple of 8.

The return address for the called function is passed in r14.

The called function will store its return value in r0.

The called function is required to preserve the values of registers r4 through r11 and register r13.

This calling convention is compatible with the Procedure Call Standard for the ARM Architecture (AAPCS).

Call Frames

Call frames have the following layout:

When a function is called, it receives a stack frame that looks like the following:

:
old frame
padding
argn
:
arg4        <-- r13 points here

The function prologue of functions generated by this code generator creates activation frames that look as follows:

:
old frame
padding
argn
:
arg4
saved r14
saved r11
:
saved r4   <-- r13 points here

Register Usage

Inside a function, registers r4..r8, r10, and r11 are used for local variables and function arguments.

r12 is used as a temporary, and r3 is used when another temporary is needed.

Direct Known Subclasses

ARMELFGenerator

Instance Method Summary collapse

Methods inherited from CommonCodeGenerator

#add, #add_function, #block, #each_statement, #emit, #features, #gensym, #has_feature?, #in_section, #output_file_name, #output_file_suffix, #real_section_name, #section, #section=, #section_alias

Constructor Details

#initialize(params) ⇒ ARMGasGenerator

Returns a new instance of ARMGasGenerator.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 62

def initialize params
  @WORDSIZE = 4
  @CODE_ALIGNMENT = 4
  @DATA_ALIGNMENT = @WORDSIZE
  @FUNCTION_ALIGNMENT = @WORDSIZE

  @NREGISTER_ARGS = 4
  @NREGISTER_LOCALS = 7
  @RETURN = :r0
  @TEMPORARY = :r12
  @constants = []
  @frame_offset = 0
  @frame_size = 0
  @function_end_label = nil
  @imports = {}
  @if_labels = []
  @saved_registers = []
  super params
  @output_file_suffix = '.s'
  @features.merge! \
    :'bits-per-word' => '32',
    :'byte-order' => 'little-endian',
    :'bytes-per-word' => 4
end

Instance Method Details

#add_constant(value) ⇒ Object

Create an entry in the constants table, returning the label that will refer to the constant. The value may be an integer or a label.



90
91
92
93
94
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 90

def add_constant value
  label = gensym
  @constants << [label, value]
  label
end

#align(alignment = nil) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 96

def align alignment = nil
  unless alignment
    # Get default alignment
    case @section
    when :code
      alignment = @CODE_ALIGNMENT
    when :data
      alignment = @DATA_ALIGNMENT
    when :function
      alignment = @FUNCTION_ALIGNMENT
    else
      # Use data alignment as default
      alignment = @DATA_ALIGNMENT
    end
  end
  emit ".align #{alignment}\n" unless alignment == 0
end

#arg_reference(n) ⇒ Object

Returns an sp-relative reference for the nth (0-based) argument.



115
116
117
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 115

def arg_reference n
  "[sp, \##{@frame_size + (n - @NREGISTER_ARGS) * @WORDSIZE}]"
end

#arg_register(n) ⇒ Object

Return the register in which the nth (0-based) argument is stored, or nil if not stored in a register



121
122
123
124
125
126
127
128
129
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 121

def arg_register n
  # The first @NREGISTER_ARGS arguments are in the v registers,
  # the rest are on the stack.
  if register_arg? n
    "v#{n + 1}"
  else
    nil
  end
end

#assymetric_binop?(op) ⇒ Boolean

Test if op is a binary operation

Returns:

  • (Boolean)


132
133
134
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 132

def assymetric_binop? op
  [:asr, :bsr, :div, :mod, :rol, :ror, :shl, :shr, :sub].member?(op)
end

#at_expr?(value) ⇒ Boolean

Test if a value is an at-expression

Returns:

  • (Boolean)


137
138
139
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 137

def at_expr? value
  value.respond_to?(:[]) && value[0] == :'@'
end

#begin_block(*code) ⇒ Object

Begins a new block.



142
143
144
145
146
147
148
149
150
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 142

def begin_block *code
  emit "# begin block\n"
  # If we are starting a block at top level, create a frame
  if @environment == @top_level
    nlocals = count_locals code
    create_frame nlocals, false
  end
  @environment = Environment.new @environment
end

#begin_function(formals, nlocals) ⇒ Object

Emit function prologue and declare formals as function arguments



153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 153

def begin_function formals, nlocals
  if @environment != @top_level
    raise "Can only begin a function at top level"
  end

  @function_end_label = gensym
  emit "# function #{formals.join ' '}\n"
  environment = Environment.new @environment
  environment.add_args formals
  @environment = environment
  emit_function_prologue formals, nlocals
end

#binop?(op) ⇒ Boolean

Test if op is a binary operation

Returns:

  • (Boolean)


167
168
169
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 167

def binop? op
  assymetric_binop?(op) || symmetric_binop?(op)
end

#byte(value) ⇒ Object

Define a byte with the given value



172
173
174
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 172

def byte value
  emit ".byte #{value}\n"
end

#call(func, *args) ⇒ Object

Call a function.



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 177

def call func, *args
  emit "# call #{func} #{args.join ' '}\n"

  # Calculate how many arguments need to be pushed on
  # the stack, and allocate space for them.
  nstack_args = number_of_stack_arguments args.length
  old_frame_offset = @frame_offset
  old_frame_size = @frame_size
  grow_frame nstack_args if nstack_args > 0

  # Put stack arguments on the stack
  (@NREGISTER_ARGS...args.length).each do |n|
    load_value_into_register args[n], @TEMPORARY
    emit "str #{@TEMPORARY}, " +
      "[sp , \##{(n - @NREGISTER_ARGS) * @WORDSIZE}]\n"
  end

  # Put register arguments in the right registers
  nregister_args = number_of_register_arguments args.length
  nregister_args.times do |n|
      load_value_into_register args[n], :"a#{n + 1}"        
  end

  # Call function
  if global? func
    emit "bl #{func}\n"
  else
    func_reg = load_value func
    emit "blx #{func_reg}\n"
  end

  # Restore original stack frame
  if old_frame_size != @frame_size
    emit "add sp, sp, \##{@frame_size - old_frame_size}\n"
    @frame_offset = old_frame_offset
    @frame_size = old_frame_size
  end
end

#common_if(comp, x, y = nil) ⇒ Object

Start a conditional using the specified branch instruction after the comparison.



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 248

def common_if comp, x, y = nil
  emit "# #{comp} #{x} #{y}\n"

  xreg = load_value x, @TEMPORARY
  yreg = load_value y, :a4

  falselabel = @environment.gensym
  @if_labels.push falselabel

  emit "cmp #{xreg}, #{yreg}\n"

  lut = { :ifeq => "bne", :ifge => "blt", :ifgt => "ble",
    :ifle => "bgt", :iflt => "bge", :ifne => "beq" }
  emit "#{lut[comp]} #{falselabel}\n"
end

#count_locals(statements) ⇒ Object

Counts the number of local variables created in a sequence of statements.



266
267
268
269
270
271
272
273
274
275
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 266

def count_locals statements
   count = 0
   each_statement(statements) do |statement|
     if statement[0] == :let
       # let introduces a single local
       count = count + 1
     end
   end
   count
end

#create_frame(nvars, save_lr = true) ⇒ Object

Creates a stack frame for the given number of arguments and local variables.



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 218

def create_frame nvars, save_lr = true
  # Calculate how many variables we will store in registers,
  # and how many on the stack.
  nregister_vars = [nvars, @NREGISTER_LOCALS].min
  nstack_vars = nvars - nregister_vars

  # Save the registers we will clobber to the stack.
  clobbered = []
  nregister_vars.times do |i|
    clobbered << :"v#{i < 5 ? i + 1 : i + 2}"
  end
  clobbered << :lr if save_lr
  @saved_registers = clobbered
  emit "stmfd sp!, {#{clobbered.join ', '}}\n"

  # Calculate frame size so that the stack pointer will
  # be properly aligned at the end of emit_function_prologue.
  @frame_size = (clobbered.length + nstack_vars) * @WORDSIZE
  if @frame_size % 8 != 0
    @frame_size = (@frame_size + 7) / 8 * 8
  end
  extra_space = @frame_size - clobbered.length * @WORDSIZE
  if extra_space > 0
    emit "sub sp, sp, \##{extra_space}\n"
  end
  @frame_offset = 0
end

#destroy_frame(ret = false) ⇒ Object

Destroys the current stack frame. If ret is true, loads the saved value of lr into pc.



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 279

def destroy_frame ret = false
  # Set sp back to where saved registers were stored
  saved = @saved_registers
  offset = @frame_size - saved.length * @WORDSIZE
  if offset != 0
    emit "add sp, sp, \##{offset}\n"
  end

  if ret
    index = saved.index :lr
    if index
      saved[index] = :pc
    else
      raise "Request to load saved lr into pc, but lr has not been saved"
    end
  end
  emit "ldmfd sp!, {#{saved.join ', '}}\n"

  emit_constants if ret
end

#emit_constantsObject

Writes any constants that need to be written to the instruction stream, and clears the list of constants that need to be written.



302
303
304
305
306
307
308
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 302

def emit_constants
  @constants.each do |x|
    label x[0]
    word x[1]
  end
  @constants = []
end

#emit_function_prologue(formals = [], nlocals = 0) ⇒ Object

Emit function prologue.



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 311

def emit_function_prologue formals = [], nlocals = 0
  # Calculate the number of arguments we were passed in
  # registers, the total number of values we need to save
  # on the stack, then create a stack frame and save
  # the v registers we will be using.
  nregister_args = [formals.length, @NREGISTER_ARGS].min
  nvars = nregister_args + nlocals
  create_frame nvars, true

  # Move arguments that were passed in registers into
  # callee-save registers.
  nregister_args.times do |i|
    emit "cpy v#{i + 1}, a#{i + 1}\n"
  end
end

#end_blockObject

Ends the current block.



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 328

def end_block
  emit "# end block\n"

  # If we are returning to top level, restore stack pointer
  # and saved registers.
  if @environment.parent == @top_level
    offset = @frame_size - @saved_registers.length * @WORDSIZE
    if offset > 0
      emit "add sp, sp, \##{offset}\n"
    end
    emit "ldmfd sp!, {#{@saved_registers.join ', '}}\n"
    @frame_size = 0
    @frame_offset = 0
    @saved_registers = []

    # If we need to emit constants, do so now
    unless @constants.empty?
      lbl = gensym
      goto lbl
      label lbl
    end
  end

  # Restore old value of @environment
  @environment = @environment.parent
end

#end_functionObject

Ends a function body.



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 356

def end_function
  if @environment == @top_level
    raise "Cannot end function when not in a function"
  end

  emit "# function epilogue\n"
  label @function_end_label

  destroy_frame true
  @frame_size = 0
  @frame_offset = 0
  @saved_registers = []
  emit "# end function\n\n"
  @environment = @top_level
end

#end_ifObject

Ends a conditional.



373
374
375
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 373

def end_if
  label @if_labels.pop
end

#eval_binop(expr, register) ⇒ Object

Evaluate the binary operation expr and store the result in register



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 378

def eval_binop expr, register
  op = expr[0]

  # Emulation for div and mod, which ARM does not have instructions for
  case op
  when :div
    func = :"__aeabi_idiv"
    import func unless @imports.has_key? func
    call func, expr[1], expr[2]
    emit "cpy #{register}, r0\n" if register != :r0
    return
  when :mod
    func = :"__aeabi_idivmod"
    import func unless @imports.has_key? func
    call func, expr[1], expr[2]
    emit "cpy #{register}, r1\n" if register != :r1
    return
  end

  x = load_value expr[1], :a4
  y = load_value expr[2], @TEMPORARY

  case op
  when :bsr
    emit "lsr #{register}, #{x}, #{y}\n"
  when :or
    emit "orr #{register}, #{x}, #{y}\n"
  when :rol
    emit "rsb #{y}, #{y}, #32\n"
    emit "ror #{register}, #{x}, #{y}\n"
  when :shl
    emit "lsl #{register}, #{x}, #{y}\n"
  when :shr
    emit "lsr #{register}, #{x}, #{y}\n"
  when :xor
    emit "eor #{register}, #{x}, #{y}\n"
  else
    emit "#{expr[0]} #{register}, #{x}, #{y}\n"
  end
end

#eval_expr(expr, register) ⇒ Object

Evaluates the expression expr and stores the result in register.



420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 420

def eval_expr expr, register
  if expr.length == 1
    # Load value
    load_value_into_register expr[0], register
  else
    # Evaluate expression
    op = expr[0]
    case op
    when :call
      call *expr[1..-1]
      emit "cpy #{register}, #{@RETURN}\n" if register != @RETURN
    when :'get-byte'
      get_byte expr[1], expr[2], register
    when :'get-word'
      get_word expr[1], expr[2], register
    when :not
      load_value_into_register expr[1], register
      emit "mvn #{@TEMPORARY}, #0\n"
      emit "eor #{register}, #{register}, #{@TEMPORARY}\n"
    else
      if binop? op
        eval_binop expr, register
      else
        raise "Not a magic word: #{op}"
      end
    end
  end
end

#export(*symbols) ⇒ Object

Export symbols from the current section



450
451
452
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 450

def export *symbols
  symbols.each { |sym| emit ".globl #{sym}\n" }
end

#function(formals, *code) ⇒ Object

Add a function to the current section



455
456
457
458
459
460
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 455

def function formals, *code
  nlocals = count_locals code
  begin_function formals, nlocals
  code.each { |action| add section, action }
  end_function
end

#get_byte(base, offset, register) ⇒ Object

Load byte from base + offset into register



463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 463

def get_byte base, offset, register
  # If base is an integer, but offset isn't, swap them
  if !integer?(offset) && integer?(base)
    base, offset = [offset, base]
  end

  if integer? offset
    base_reg = load_value base
    if offset == 0
      emit "ldrb #{register}, [#{base_reg}]\n"
    else
      emit "ldrb #{register}, [#{base_reg}, \##{offset}]\n"
    end
  else
    base_reg = load_value base
    offset_reg = load_value offset, :a4
    emit "ldrb #{register}, [#{base_reg}, #{offset_reg}]\n"
  end
end

#get_word(base, offset, register) ⇒ Object

Load word from base + offset * _@WORDSIZE_ into register



484
485
486
487
488
489
490
491
492
493
494
495
496
497
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 484

def get_word base, offset, register
  if integer? offset
    base_reg = load_value base
    if offset == 0
      emit "ldr #{register}, [#{base_reg}]\n"
    else
      emit "ldr #{register}, [#{base_reg}, \##{offset * @WORDSIZE}]\n"
    end
  else
    base_reg = load_value base
    offset_reg = load_value offset, :a4
    emit "ldr #{register}, [#{base_reg}, #{offset_reg}, LSL #2]\n"
  end
end

#global?(symbol) ⇒ Boolean

Test if a symbol refers to a global

Returns:

  • (Boolean)


500
501
502
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 500

def global? symbol
  symbol?(symbol) && @environment[symbol] == nil
end

#goto(label) ⇒ Object

Jump to a label.



505
506
507
508
509
510
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 505

def goto label
  emit "b #{label}\n"

  # If we have constants that need to be emitted, do so now
  emit_constants
end

#grow_frame(nwords) ⇒ Object

Grows the current frame by n words, plus padding to respect alignment rules.



514
515
516
517
518
519
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 514

def grow_frame nwords
  increment = (nwords * @WORDSIZE + 7) / 8 * 8
  emit "sub sp, sp, \##{increment}\n"
  @frame_size = @frame_size + increment
  @frame_offset = @frame_offset + increment
end

#ifelseObject

Start the false path of a conditional.



522
523
524
525
526
527
528
529
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 522

def ifelse
  emit "# else\n"
  newlabel = @environment.gensym
  goto newlabel
  lbl = @if_labels.pop
  label lbl
  @if_labels.push newlabel
end

#ifeq(x, y) ⇒ Object

Test if x is equal to y



532
533
534
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 532

def ifeq x, y
  common_if :ifeq, x, y
end

#ifge(x, y) ⇒ Object

Test if x is greater than or equal to y



537
538
539
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 537

def ifge x, y
  common_if :ifge, x, y
end

#ifgt(x, y) ⇒ Object

Test if x is strictly greater than y



542
543
544
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 542

def ifgt x, y
  common_if :ifgt, x, y
end

#ifle(x, y) ⇒ Object

Test if x is less than or equal to y



547
548
549
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 547

def ifle x, y
  common_if :ifle, x, y
end

#iflt(x, y) ⇒ Object

Test if x is strictly less than y



552
553
554
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 552

def iflt x, y
  common_if :iflt, x, y
end

#ifne(x, y) ⇒ Object

Test if x different from y



557
558
559
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 557

def ifne x, y
  common_if :ifne, x, y
end

#import(*symbols) ⇒ Object

Import labels into the current section



562
563
564
565
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 562

def import *symbols
  # Record imported labels in @imports
  symbols.each { |sym| @imports[sym] = sym }
end

#integer?(value) ⇒ Boolean

Test if a value is an integer

Returns:

  • (Boolean)


568
569
570
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 568

def integer? value
  value.kind_of? Integer
end

#label(name) ⇒ Object

Emit a label



573
574
575
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 573

def label name
  emit "#{name}:\n"
end

#let(symbol, *expr) ⇒ Object

Introduce a new local variable



578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 578

def let symbol, *expr
  emit "# let #{symbol} #{expr.join ' '}\n"
  n = @environment.locals
  @environment.add_local symbol

  register = local_register n
  if register
    # We will use a register to store the value
    eval_expr expr, register
  else
    # We will use the stack to store the value
    ref = local_reference n
    eval_expr expr, @TEMPORARY
    emit "str #{@TEMPORARY}, #{ref}\n"
  end
end

#load_at(address, register = @TEMPORARY) ⇒ Object

Load the value at the given address.



596
597
598
599
600
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 596

def load_at address, register = @TEMPORARY
  load_value_into_register address, register
  emit "ldr #{register}, [#{register}]\n"
  register
end

#load_value(x, register = @TEMPORARY) ⇒ Object

Load a value into a register. Returns the name of the register. If the value was already in a register, the name of that register is returned. Else, the value is loaded into a register and the name of that register is returned. The register to use in that case may be specified using the optional second argument.



609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 609

def load_value x, register = @TEMPORARY
  if integer? x
    if x >= 0 && x <= 255
      emit "mov #{register}, \##{x}\n"
      return register
    elsif x >= -255 && x < 0
      emit "mvn #{register}, \##{-(x + 1)}\n"
      return register
    else
      lbl = add_constant x
      emit "ldr #{register}, #{lbl}\n"
      return register
    end
  elsif symbol? x
    binding = @environment[x]
    if binding
      case binding[0]
      when :arg
        n = binding[1]
        if register_arg? n
          return arg_register(n)
        else
          emit "ldr #{register}, #{arg_reference binding[1]}\n"
          return register
        end
      when :local
        n = binding[1]
        if register_local? n
          return local_register(n)
        else
          emit "ldr #{register}, #{local_reference n}\n"
          return register
        end
      else
        raise "Don't know how to load #{x.inspect}"
      end
    else
      # Assume global
      lbl = add_constant x
      emit "ldr #{register}, #{lbl}\n"
      return register
    end
  elsif at_expr? x
    load_at x[1], register
  else
    raise "Don't know how to load #{x.inspect}"
  end
end

#load_value_into_register(x, register) ⇒ Object

Load a value into a specific register



659
660
661
662
663
664
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 659

def load_value_into_register x, register
  reg = load_value x, register
  if reg != register
    emit "cpy #{register}, #{reg}\n"
  end
end

#local_reference(n) ⇒ Object

Returns an sp-relative reference for the nth (0-based) local.



667
668
669
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 667

def local_reference n
  "[sp, \##{@frame_offset + (number_of_register_arguments + n) * @WORDSIZE}]"
end

#local_register(n) ⇒ Object

Return the register in which the nth local (0-based) is stored, or nil if not stored in a register



673
674
675
676
677
678
679
680
681
682
683
684
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 673

def local_register n
  if register_local? n
    n = n + number_of_register_arguments
    if n < 5
      "v#{n + 1}"
    else
      "v#{n + 2}"
    end
  else
    nil
  end
end

#number_of_register_arguments(n = @environment.args) ⇒ Object

Calculate the number of register arguments, given the total number of arguments.



688
689
690
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 688

def number_of_register_arguments n = @environment.args
  [n, @NREGISTER_ARGS].min
end

#number_of_stack_arguments(n = @environment.args) ⇒ Object

Calculate the number of stack arguments, given the total number of arguments.



694
695
696
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 694

def number_of_stack_arguments n = @environment.args
  [0, n - @NREGISTER_ARGS].max
end

#register_arg?(n) ⇒ Boolean

Returns true if the nth (0-based) argument is stored in a register

Returns:

  • (Boolean)


699
700
701
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 699

def register_arg? n
  n < @NREGISTER_ARGS
end

#register_local?(n) ⇒ Boolean

Returns true if the nth (0-based) local is stored in a register

Returns:

  • (Boolean)


704
705
706
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 704

def register_local? n
  (n + number_of_register_arguments) < @NREGISTER_LOCALS
end

#ret(*words) ⇒ Object

Returns from a function.

words may contain an expression to be evaluated. The result of the evaluation is returned from the function.



712
713
714
715
716
717
718
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 712

def ret *words
  emit "# return #{words.join ' '}\n"
  # Compute return value and store it in @RETURN
  eval_expr(words, @RETURN) unless words.empty?
  # Go to epilogue
  goto @function_end_label
end

#set(symbol, *expr) ⇒ Object

Set a variable to the result of evaluating an expression



721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 721

def set symbol, *expr
  emit "# set #{symbol} #{expr.join ' '}\n"

  x = @environment[symbol]
  if x == nil
    raise "Cannot change value of constant #{symbol}"
  end

  register = nil
  case x[0]
  when :arg
    register = arg_register x[1]
  when :local
    register = local_register x[1]
  end

  if register
    # Set new value
    eval_expr expr, register
  else
    case x[0]
    when :local
      ref = local_reference x[1]
    when :arg
      ref = arg_reference x[1]
    else
      raise "??? #{sym} is neither a local nor an argument"
    end
    eval_expr expr, @TEMPORARY
    emit "str #{@TEMPORARY}, #{ref}\n"
  end
end

#set_byte(base, offset, value) ⇒ Object

Set the byte at base + offset to value



755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 755

def set_byte base, offset, value
  emit "# set-byte #{base} #{offset} #{value}\n"
  # If base is an integer, but offset isn't, swap them
  if !integer?(offset) && integer?(base)
    base, offset = [offset, base]
  end

  if integer? offset
    base_reg = load_value base, :a4
    load_value_into_register value, @TEMPORARY
    if offset == 0
      emit "strb #{@TEMPORARY}, [#{base_reg}]\n"
    else
      emit "strb #{@TEMPORARY}, [#{base_reg}, \##{offset}]\n"
    end
  else
    eval_binop [:add, base, offset], :a4
    load_value_into_register value, @TEMPORARY
    emit "strb #{@TEMPORARY}, [a4]\n"
  end
end

#set_word(base, offset, value) ⇒ Object

Set the word at base + offset * @WORDSIZE to value



778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 778

def set_word base, offset, value
  emit "# set-word #{base} #{offset} #{value}\n"
  # If base is an integer, but offset isn't, swap them
  if !integer?(offset) && integer?(base)
    base, offset = [offset, base]
  end

  if integer? offset
    base_reg = load_value base, :a4
    load_value_into_register value, @TEMPORARY
    if offset == 0
      emit "str #{@TEMPORARY}, [#{base_reg}]\n"
    else
      emit "str #{@TEMPORARY}, [#{base_reg}, \##{offset * @WORDSIZE}]\n"
    end
  else
	load_value_into_register base, :a4
    load_value_into_register offset, @TEMPORARY
    emit "add a4, a4, #{@TEMPORARY}, LSL #2\n"
    load_value_into_register value, @TEMPORARY
    emit "str #{@TEMPORARY}, [a4]\n"
  end
end

#string(value) ⇒ Object

Define a string with the given value



803
804
805
806
807
808
809
810
811
812
813
814
815
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 803

def string value
  code = ''
  value.each_byte do |b|
    if b == 92
      code << "\\\\"
    elsif b >= 32 && b < 127 && b != 34
      code << b.chr
    else
      code << sprintf("\\%03o", b)
    end
  end
  emit ".ascii \"#{code}\"\n"
end

#symbol?(value) ⇒ Boolean

Test if a value is a symbol

Returns:

  • (Boolean)


818
819
820
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 818

def symbol? value
  value.kind_of? Symbol
end

#symmetric_binop?(op) ⇒ Boolean

Test if op is a symmetric binary operation (i.e. it will yield the same result if the order of its source operands is changed).

Returns:

  • (Boolean)


824
825
826
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 824

def symmetric_binop? op
  [:add, :and, :mul, :or, :xor].member? op
end

#tail_call(func, *args) ⇒ Object

Call a function, re-using the current call frame if possible.



829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 829

def tail_call func, *args
  emit "# tail-call #{func} #{args.join ' '}\n"      

  # Compute number of stack arguments
  nstackargs = number_of_stack_arguments args.length
  # If we need more stack arguments than we have now,
  # perform a normal call and return
  if nstackargs > number_of_stack_arguments(@environment.args)
    emit "# Not enough space for proper tail call; using regular call\n"
    ret :call, func, *args
  end

  # We will assign arguments from left to right.
  # Find places that we will overwrite before we read them,
  # and store their values in some newly allocated stack space.
  old_frame_offset = @frame_offset
  old_frame_size = @frame_size
  overwritten = {}
  (@NREGISTER_ARGS...args.length).each do |i|
    arg = args[i]
    arg = arg[1] if at_expr? arg
    if symbol?(arg)
      binding = @environment[arg]
      if binding[0] == :arg && binding[1] >= @NREGISTER_ARGS &&
          binding[1] < i
        # Argument i is a stack argument, but the value we will assign
        # it is a stack argument that comes before it, so we will
        # have overwritten it by the time we get to it.
        overwritten[arg] = nil
      end
    end
  end
  
  unless overwritten.empty?
    # Allocate space for arguments to be saved
    grow_frame overwritten.length
    # Save values
    offset = 0
    overwritten.each_key do |key|
      reg = load_value key
      emit "str #{reg}, [sp, \##{offset}]\n"
      overwritten[key] = offset
      offset = offset + @WORDSIZE
    end
  end

  # Assign arguments
  args.each_index do |i|
    arg = args[i]
    if register_arg? i
      load_value_into_register arg, "a#{i + 1}"
    else
      # Test if this is a value we saved
      sym = at_expr?(arg) ? arg[1] : arg
      saved = overwritten[sym]
      if saved
        # Saved value, load from stack
        reg = @TEMPORARY
        emit "ldr #{reg}, [sp, \##{saved}]\n"
      else
        # Regular value, use load_value
        reg = load_value arg
      end
      emit "str #{reg}, #{arg_reference i}\n"
    end
  end

  # Load address of function to be called into @TEMPORARY
  load_value_into_register func, @TEMPORARY

  # Destroy current activation frame and enter func
  destroy_frame false
  emit "bx #{@TEMPORARY}\n"
  emit_constants
end

#word(value) ⇒ Object

Define a word with the given value



906
907
908
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 906

def word value
  emit ".int #{value}\n"
end

#write(io) ⇒ Object

Write generated code to the given IO object.



911
912
913
914
915
916
917
918
919
# File 'lib/voodoo/generators/arm_gas_generator.rb', line 911

def write io
  @sections.each do |section,code|
    unless code.empty?
      io.puts ".section #{section.to_s}"
      io.puts code
      io.puts
    end
  end
end