bit of renaming , cleaning and documentation

This commit is contained in:
Torsten Ruger 2014-04-25 13:29:12 +03:00
parent 6261451c4b
commit f1a7993b47
10 changed files with 89 additions and 44 deletions

View File

@ -1,12 +1,7 @@
Assembler in Ruby Assembler in Ruby
================= =================
Supporting arm, but aimed quite specifically at raspberry pi, arm v7, floating point included Supporting arm, but aimed quite specifically at raspberry pi, arm v7, floating point included (later)
Outputs Elf object files, with relocation support.
Constant table support exists but isn't very good. Some addressing modes
are not supported or only partially supported.
Supported (pseudo)instructions: Supported (pseudo)instructions:
@ -14,5 +9,5 @@ Supported (pseudo)instructions:
mov, mvn, strb, str, ldrb, ldr, push, pop, b, bl, bx, swi mov, mvn, strb, str, ldrb, ldr, push, pop, b, bl, bx, swi
- Conditional versions of above - Conditional versions of above
Thanks to Cyndis for starting this arm/elf project in the first place: https://github.com/cyndis/as Thanks to Mikko for starting this arm/elf project in the first place: https://github.com/cyndis/as

View File

@ -16,12 +16,12 @@ module Asm
end end
def initialize def initialize
@values = [] @codes = []
@position = 0 # marks not set @position = 0 # marks not set
@labels = [] @labels = []
@string_table = {} @string_table = {}
end end
attr_reader :values , :position attr_reader :codes , :position
def instruction(clazz,name, *args) def instruction(clazz,name, *args)
opcode = name.to_s opcode = name.to_s
@ -39,7 +39,7 @@ module Asm
raise "Invalid argument #{arg.inspect} for instruction" raise "Invalid argument #{arg.inspect} for instruction"
end end
end end
add_value clazz.new(opcode , arg_nodes) add_code clazz.new(opcode , arg_nodes)
end end
@ -85,7 +85,7 @@ module Asm
#put the strings at the end of the assembled code. #put the strings at the end of the assembled code.
# adding them will fix their position and make them assemble after # adding them will fix their position and make them assemble after
@string_table.values.each do |data| @string_table.values.each do |data|
add_value data add_code data
end end
io = StringIO.new io = StringIO.new
assemble(io) assemble(io)
@ -93,8 +93,8 @@ module Asm
end end
def add_string str def add_string str
value = @string_table[str] code = @string_table[str]
return value if value return code if code
data = Asm::StringLiteral.new(str) data = Asm::StringLiteral.new(str)
@string_table[str] = data @string_table[str] = data
end end
@ -103,11 +103,11 @@ module Asm
@string_table.values @string_table.values
end end
def add_value(val) def add_code(kode)
val.at(@position) kode.at(@position)
length = val.length length = kode.length
@position += length @position += length
@values << val @codes << kode
end end
def label name def label name
@ -116,12 +116,8 @@ module Asm
label label
end end
def label! name
label(name).set!
end
def assemble(io) def assemble(io)
@values.each do |obj| @codes.each do |obj|
obj.assemble io obj.assemble io
end end
end end

View File

@ -1,15 +1,23 @@
module Asm module Asm
# ADDRESSING MODE 4 , Calling # There are only three call instructions in arm branch (b), call (bl) and syscall (swi)
# A branch could be called a jump as it has no notion of returning
# A call has the bl code as someone thought "branch with link" is a useful name.
# The pc is put into the link register to make a return possible
# a return is affected by moving the stored link register into the pc, effectively a branch
# swi (SoftWareInterrupt) or system call is how we call the kernel.
# in Arm the register layout is different and so we have to place the syscall code into register 7
# Registers 0-6 hold the call values as for a normal c call
class CallInstruction < Instruction class CallInstruction < Instruction
include Asm::InstructionTools
def initialize(opcode , args) def initialize(opcode , args)
super(opcode,args) super(opcode,args)
end end
def assemble(io) def assemble(io)
s = @update_status_flag? 1 : 0
case opcode case opcode
when :b, :bl when :b, :bl
arg = args[0] arg = args[0]

View File

@ -34,11 +34,18 @@ module Asm
@args = args @args = args
@operand = 0 @operand = 0
end end
attr_reader :opcode, :args , :position , :cond , :operand , :update_status_flag
attr_reader :opcode, :args
# Many arm instructions may be conditional, where the default condition is always (al)
# InstructionTools::COND_CODES names them, and this attribute reflects it
attr_reader :cond
attr_reader :operand
def affect_status # Logic instructions may be executed with or without affecting the status register
@s # Only when an instruction affects the status is a subsequent compare instruction effective
end # But to make the conditional execution (see cond) work for more than one instruction, one needs to
# be able to execute without changing the status
attr_reader :update_status_flag
# arm intrucioons are pretty sensible, and always 4 bytes (thumb not supported) # arm intrucioons are pretty sensible, and always 4 bytes (thumb not supported)
def length def length

View File

@ -44,10 +44,6 @@ module Asm
COND_CODES[@cond] or throw "no code found for #{@cond}" COND_CODES[@cond] or throw "no code found for #{@cond}"
end end
OPC_DATA_PROCESSING = 0b00
OPC_MEMORY_ACCESS = 0b01
OPC_STACK = 0b10
REGISTERS = { 'r0' => 0, 'r1' => 1, 'r2' => 2, 'r3' => 3, 'r4' => 4, 'r5' => 5, REGISTERS = { 'r0' => 0, 'r1' => 1, 'r2' => 2, 'r3' => 3, 'r4' => 4, 'r5' => 5,
'r6' => 6, 'r7' => 7, 'r8' => 8, 'r9' => 9, 'r10' => 10, 'r11' => 11, 'r6' => 6, 'r7' => 7, 'r8' => 8, 'r9' => 9, 'r10' => 10, 'r11' => 11,
'r12' => 12, 'r13' => 13, 'r14' => 14, 'r15' => 15, 'a1' => 0, 'a2' => 1, 'r12' => 12, 'r13' => 13, 'r14' => 14, 'r15' => 15, 'a1' => 0, 'a2' => 1,
@ -60,7 +56,7 @@ module Asm
end end
def calculate_u8_with_rr(arg) def calculate_u8_with_rr(arg)
parts = arg.value.to_s(2).rjust(32,'0').scan(/^(0*)(.+?)0*$/).flatten parts = arg.value.to_s(2).rjust(32,'0').scan(/^(0*)(.+?)0*$/).flatten
pre_zeros = parts[0].length pre_zeros = parts[0].length

View File

@ -7,11 +7,11 @@ module Asm
def initialize( opcode , args) def initialize( opcode , args)
super(opcode , args) super(opcode , args)
@inst_class = OPC_DATA_PROCESSING @rn = nil
@i = 0 @i = 0
@rd = args[0] @rd = args[0]
end end
attr_accessor :inst_class, :i, :rn, :rd attr_accessor :i, :rn, :rd
# Build representation for source value # Build representation for source value
def build def build
@ -71,13 +71,14 @@ module Asm
def assemble(io) def assemble(io)
build build
instuction_class = 0b00 # OPC_DATA_PROCESSING
val = operand.is_a?(Register) ? operand.bits : operand val = operand.is_a?(Register) ? operand.bits : operand
val |= (rd.bits << 12) val |= (rd.bits << 12)
val |= (rn.bits << 12+4) val |= (rn.bits << 12+4)
val |= (update_status_flag << 12+4+4)#20 val |= (update_status_flag << 12+4+4)#20
val |= (op_bit_code << 12+4+4 +1) val |= (op_bit_code << 12+4+4 +1)
val |= (i << 12+4+4 +1+4) val |= (i << 12+4+4 +1+4)
val |= (inst_class << 12+4+4 +1+4+1) val |= (instuction_class << 12+4+4 +1+4+1)
val |= (cond_bit_code << 12+4+4 +1+4+1+2) val |= (cond_bit_code << 12+4+4 +1+4+1+2)
io.write_uint32 val io.write_uint32 val
end end

View File

@ -8,7 +8,6 @@ module Asm
def initialize(opcode , args) def initialize(opcode , args)
super( opcode , args ) super( opcode , args )
@inst_class = OPC_MEMORY_ACCESS
@i = 0 #I flag (third bit) @i = 0 #I flag (third bit)
@pre_post_index = 0 #P flag @pre_post_index = 0 #P flag
@add_offset = 0 #U flag @add_offset = 0 #U flag
@ -18,7 +17,7 @@ module Asm
@rn = reg "r0" # register zero = zero bit pattern @rn = reg "r0" # register zero = zero bit pattern
@rd = reg "r0" # register zero = zero bit pattern @rd = reg "r0" # register zero = zero bit pattern
end end
attr_accessor :inst_class, :i, :pre_post_index, :add_offset, attr_accessor :i, :pre_post_index, :add_offset,
:byte_access, :w, :is_load, :rn, :rd :byte_access, :w, :is_load, :rn, :rd
# Build representation for target address # Build representation for target address
@ -63,6 +62,7 @@ module Asm
# so it doesn't matter. Will see # so it doesn't matter. Will see
@add_offset = 1 @add_offset = 1
@pre_post_index = 1 @pre_post_index = 1
instuction_class = 0b01 # OPC_MEMORY_ACCESS
val = operand val = operand
val |= (rd.bits << 12 ) val |= (rd.bits << 12 )
val |= (rn.bits << 12+4) #16 val |= (rn.bits << 12+4) #16
@ -72,7 +72,7 @@ module Asm
val |= (add_offset << 12+4 +4+1+1+1) val |= (add_offset << 12+4 +4+1+1+1)
val |= (pre_post_index << 12+4 +4+1+1+1+1)#24 val |= (pre_post_index << 12+4 +4+1+1+1+1)#24
val |= (i << 12+4 +4+1+1+1+1 +1) val |= (i << 12+4 +4+1+1+1+1 +1)
val |= (inst_class << 12+4 +4+1+1+1+1 +1+1) val |= (instuction_class<<12+4 +4+1+1+1+1 +1+1)
val |= (cond_bit_code << 12+4 +4+1+1+1+1 +1+1+2) val |= (cond_bit_code << 12+4 +4+1+1+1+1 +1+1+2)
io.write_uint32 val io.write_uint32 val
end end

View File

@ -7,7 +7,6 @@ module Asm
def initialize(opcode , args) def initialize(opcode , args)
super(opcode,args) super(opcode,args)
@inst_class = Asm::Instruction::OPC_STACK
@update_status_flag= 0 @update_status_flag= 0
@rn = reg "r0" # register zero = zero bit pattern @rn = reg "r0" # register zero = zero bit pattern
# downward growing, decrement before memory access # downward growing, decrement before memory access
@ -23,11 +22,12 @@ module Asm
@is_pop = 1 @is_pop = 1
end end
end end
attr_accessor :cond, :inst_class, :pre_post_index, :up_down, attr_accessor :pre_post_index, :up_down,
:update_status_flag, :write_base, :is_pop, :rn, :operand :update_status_flag, :write_base, :is_pop, :rn
def assemble(io) def assemble(io)
build build
instuction_class = 0b10 # OPC_STACK
cond = @cond.is_a?(Symbol) ? COND_CODES[@cond] : @cond cond = @cond.is_a?(Symbol) ? COND_CODES[@cond] : @cond
rn = reg "sp" # sp register rn = reg "sp" # sp register
#assemble of old #assemble of old
@ -38,7 +38,7 @@ module Asm
val |= (update_status_flag << 16+4+ 1+1) val |= (update_status_flag << 16+4+ 1+1)
val |= (up_down << 16+4+ 1+1+1) val |= (up_down << 16+4+ 1+1+1)
val |= (pre_post_index << 16+4+ 1+1+1+1)#24 val |= (pre_post_index << 16+4+ 1+1+1+1)#24
val |= (inst_class << 16+4+ 1+1+1+1 +2) val |= (instuction_class << 16+4+ 1+1+1+1 +2)
val |= (cond << 16+4+ 1+1+1+1 +2+2) val |= (cond << 16+4+ 1+1+1+1 +2+2)
io.write_uint32 val io.write_uint32 val
end end

18
lib/elf/README.markdown Normal file
View File

@ -0,0 +1,18 @@
Minimal elf support
===================
This is really minnimal and works only for our current use case
- no external functions (all syscalls)
- only position independant code (no relocation)
- embedded data (into text), no data section
I was close to going the wilson way, ie assmble, load into memory and jump
But it is nice to produce executables. Also easier to test, what with segfaults and such.
Executalbe files are not supported (yet?), but object files work. So the only thing that remains is to
call the linker on the produced object file. The resulting file is an executable that actually works!!
Thanks to Mikko for starting this arm/elf project in the first place: https://github.com/cyndis/as
This part definately needs tlc, so anyone who is interested, dig in!

24
lib/vm/README.markdown Normal file
View File

@ -0,0 +1,24 @@
Parser
================
This includes the parser and generated ast.
Parslet is really great in that it:
- does not generate code but instean gives a clean dsl to define a grammar
- uses ruby modules so one can split the grammars up
- has a seperate tranform stage to generate an ast layer
Especially the last point is great. Since it is seperate it does not clutter up the actual grammar.
And it can generate a layer that has no links to the actual parser anymore, thus saving/automating
a complete tranformation process.
Virtual Machine
===============
This is the logic that uses the generated ast to produce code, using the asm layer.
Apart from shuffeling things around from one layer to the other, it keeps track about registers and
provides the stack glue. All the stuff a compiler would usually do.
Also all syscalls are abstracted as functions.