kompiler 0.3.0.pre.3 → 0.3.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.
- checksums.yaml +4 -4
- data/bin/kompile +230 -33
- data/lib/kompiler/arch_manager.rb +11 -2
- data/lib/kompiler/architecture.rb +5 -1
- data/lib/kompiler/architectures/armv8a/instructions.rb +76 -27
- data/lib/kompiler/architectures/armv8a/load.rb +3 -1
- data/lib/kompiler/architectures/armv8a/simd_fp_instructions.rb +1308 -0
- data/lib/kompiler/architectures/armv8a/simd_fp_registers.rb +23 -0
- data/lib/kompiler/architectures/armv8a/sys_registers.rb +3 -0
- data/lib/kompiler/compiler_functions.rb +138 -219
- data/lib/kompiler/config.rb +40 -0
- data/lib/kompiler/directives.rb +367 -4
- data/lib/kompiler/math_ast.rb +693 -0
- data/lib/kompiler/mc_builder.rb +48 -0
- data/lib/kompiler/parsers.rb +137 -45
- data/lib/kompiler/wrappers/elf_wrapper.rb +499 -0
- data/lib/kompiler/wrappers/packed_bytes.rb +68 -0
- data/lib/kompiler/wrappers.rb +1 -0
- data/lib/kompiler.rb +4 -1
- metadata +10 -3
@@ -0,0 +1,23 @@
|
|
1
|
+
# Copyright 2024 Kyrylo Shyshko
|
2
|
+
# Licensed under the Apache License, Version 2.0. See LICENSE file for details.
|
3
|
+
|
4
|
+
module Kompiler
|
5
|
+
|
6
|
+
module ARMv8A
|
7
|
+
|
8
|
+
def self.simd_fp_registers
|
9
|
+
@simd_fp_registers
|
10
|
+
end
|
11
|
+
|
12
|
+
@simd_fp_registers = [
|
13
|
+
|
14
|
+
]
|
15
|
+
|
16
|
+
(0..31).each do |reg_i|
|
17
|
+
@simd_fp_registers << {reg_name: "q#{reg_i}", reg_type: "simd_fp_reg", re_num: reg_i}
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end # Kompiler::ARMv8A
|
22
|
+
|
23
|
+
end # Kompiler
|
@@ -54,6 +54,9 @@ end
|
|
54
54
|
|
55
55
|
{reg_name: "ISR_EL1", reg_size: 64, reg_type: "sr", reg_encoding: {"op0"=>0b11, "op1"=>0b000, "CRn"=>0b1100, "CRm"=>0b0001, "op2"=>0b000}},
|
56
56
|
|
57
|
+
{reg_name: "CPACR_EL1", reg_size: 64, reg_type: "sr", reg_encoding: {"op0"=>0b11, "op1"=>0b000, "CRn"=>0b0001, "CRm"=>0b0000, "op2"=>0b010}},
|
58
|
+
{reg_name: "CPACR_EL12", reg_size: 64, reg_type: "sr", reg_encoding: {"op0"=>0b11, "op1"=>0b101, "CRn"=>0b0001, "CRm"=>0b0000, "op2"=>0b010}},
|
59
|
+
|
57
60
|
# Special registers for the MSR (immediate) instruction (some of them were previously defined already)
|
58
61
|
{reg_name: "SPSel", reg_type: "pstate_reg"},
|
59
62
|
{reg_name: "DAIFSet", reg_type: "pstate_reg"},
|
@@ -1,226 +1,49 @@
|
|
1
1
|
# Copyright 2024 Kyrylo Shyshko
|
2
2
|
# Licensed under the Apache License, Version 2.0. See LICENSE file for details.
|
3
3
|
|
4
|
+
#
|
5
|
+
# Implements main logic happening during compilation.
|
6
|
+
# Main functions:
|
7
|
+
# parse_code - main code parser that turns all lines into an abstract structure (determining types of lines and connecting them into a program)
|
8
|
+
# construct_program_mc - transforms the parse_code's AST into machine code (MC)
|
9
|
+
# detailed_compile - stitches parse_code and construct_program_mc to fully compile a program with a detailed output
|
10
|
+
# compile - calls detailed_compile and removes the extra output information
|
11
|
+
#
|
12
|
+
|
13
|
+
|
4
14
|
module Kompiler
|
5
15
|
|
6
16
|
module CompilerFunctions
|
7
17
|
|
8
|
-
def self.
|
9
|
-
|
10
|
-
final_lines = lines.dup
|
18
|
+
def self.parse_code(lines)
|
11
19
|
|
12
|
-
|
20
|
+
parsed_lines = []
|
13
21
|
|
14
|
-
|
15
|
-
break if line_i >= final_lines.size
|
16
|
-
|
17
|
-
line = final_lines[line_i]
|
18
|
-
|
19
|
-
keyword, operands = Kompiler::Parsers.parse_instruction_line(line)
|
20
|
-
|
21
|
-
if keyword == false
|
22
|
-
line_i += 1
|
23
|
-
next
|
24
|
-
end
|
25
|
-
|
26
|
-
if keyword.start_with? "."
|
27
|
-
keyword = keyword[1..]
|
28
|
-
end
|
29
|
-
|
30
|
-
if !["load", "include", "load_end", "include_end"].include?(keyword)
|
31
|
-
line_i += 1
|
32
|
-
next
|
33
|
-
end
|
34
|
-
|
35
|
-
raise "Incorrect use of the \"#{keyword}\" directive: requires a filename in a string." if operands.size != 1 || (operands.size == 1 && operands[0][:type] != "string")
|
36
|
-
|
37
|
-
load_file_name_selector = operands[0][:string]
|
38
|
-
|
39
|
-
|
40
|
-
# Remove the include code line from the lines array
|
41
|
-
final_lines.delete_at line_i
|
42
|
-
|
43
|
-
# If ends with _end, that means that the file contents should be appended at the end of the current lines
|
44
|
-
if keyword.end_with? "_end"
|
45
|
-
next_i_insert = final_lines.size
|
46
|
-
else
|
47
|
-
# If doesn't end with _end, files should be loaded in-place of the current line
|
48
|
-
next_i_insert = line_i
|
49
|
-
end
|
50
|
-
|
51
|
-
|
52
|
-
Dir[load_file_name_selector].each do |load_file_name|
|
53
|
-
# Get the absolute path filename
|
54
|
-
full_file_name = File.expand_path(load_file_name)
|
55
|
-
|
56
|
-
raise "#{keyword} \"#{load_file_name}\": File not found." if !File.exist?(full_file_name)
|
57
|
-
|
58
|
-
# Check if the file was already loaded (stop recursive loading)
|
59
|
-
if loaded_files.include?(full_file_name)
|
60
|
-
next
|
61
|
-
end
|
62
|
-
|
63
|
-
# Read the file to load
|
64
|
-
include_code = File.read(full_file_name)
|
65
|
-
|
66
|
-
# Separate the lines inside it
|
67
|
-
include_code_lines = Kompiler::Parsers.get_code_lines(include_code)
|
68
|
-
|
69
|
-
# Add the lines from the load file to the lines array at the line_i index, effectively replacing the load command with the content of the load file
|
70
|
-
final_lines.insert next_i_insert, *include_code_lines
|
71
|
-
|
72
|
-
next_i_insert += include_code_lines.size
|
73
|
-
|
74
|
-
# Add the filename (absolute path) to the list of included files
|
75
|
-
loaded_files << full_file_name
|
76
|
-
end
|
77
|
-
|
78
|
-
# Don't increment line_i, since the new loop will now start include-parsing the newly loaded file in the same program
|
79
|
-
end
|
80
|
-
|
81
|
-
final_lines
|
22
|
+
instr_adr = 0
|
82
23
|
|
83
|
-
end
|
84
|
-
|
85
|
-
|
86
|
-
def self.parse_macros(lines)
|
87
|
-
|
88
|
-
macros = []
|
89
|
-
|
90
24
|
line_i = 0
|
91
|
-
|
92
|
-
loop do
|
93
|
-
break if line_i >= lines.size
|
94
|
-
|
95
|
-
line = lines[line_i]
|
96
|
-
|
97
|
-
keyword, operands = Kompiler::Parsers.parse_instruction_line line
|
98
|
-
|
99
|
-
keyword = keyword[1..] if keyword.start_with? "."
|
100
|
-
|
101
|
-
if keyword == 'macro'
|
102
|
-
raise "Macro definition error: Expected two operands to be provided, while #{operands.size} were given" if operands.size != 2
|
103
|
-
raise "Macro definition error: The macro name must either be a word or a string" if !["label", "string"].include?(operands[0][:type])
|
104
|
-
|
105
|
-
case operands[0][:type]
|
106
|
-
when "label"
|
107
|
-
macro_name = operands[0][:value]
|
108
|
-
when "string"
|
109
|
-
macro_name = operands[0][:value]
|
110
|
-
end
|
111
|
-
|
112
|
-
macro_definition = operands[1][:definition]
|
113
|
-
|
114
|
-
macros << {name: macro_name, definition: macro_definition}
|
115
|
-
|
116
|
-
lines.delete_at line_i
|
117
|
-
else
|
118
|
-
line_i += 1
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# Apply the macros to all lines, and then the lines that were changed, and than again, and so on (iterative loop until none of the lines need to be checked)
|
123
|
-
|
124
|
-
lines_to_check = (0...lines.size).to_a
|
125
|
-
|
126
|
-
while lines_to_check.size != 0 # Repeat until no more lines to check
|
127
|
-
|
128
|
-
# Define restricted separators characters (characters that CAN'T separate a macro from everything else)
|
129
|
-
restricted_separator_chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + ["_", "."]
|
130
|
-
|
131
|
-
# Create an array for the lines to check once again
|
132
|
-
new_lines_to_check = []
|
133
|
-
|
134
|
-
lines_to_check.each do |line_i|
|
135
|
-
line = lines[line_i]
|
136
|
-
|
137
|
-
char_i = 0
|
138
|
-
|
139
|
-
# Create a variable to indicate if a macro was used on the current line
|
140
|
-
line_macro_used = false
|
141
|
-
|
142
|
-
loop do
|
143
|
-
break if char_i >= line.size
|
144
|
-
|
145
|
-
if ['"', "'"].include? line[char_i]
|
146
|
-
# If a string, skip it
|
147
|
-
str, parsed_length = Kompiler::Parsers.parse_str line[char_i..]
|
148
|
-
char_i += parsed_length
|
149
|
-
next
|
150
|
-
end
|
151
|
-
|
152
|
-
cut_line = line[char_i..]
|
153
|
-
|
154
|
-
# Create a variable to indicate if a macro was used in the current position
|
155
|
-
macro_used = false
|
156
|
-
|
157
|
-
macros.each do |macro|
|
158
|
-
macro_name = macro[:name]
|
159
|
-
macro_def = macro[:definition]
|
160
|
-
next if !cut_line.start_with?(macro_name) # Skip if doesn't begin with the macro's name
|
161
|
-
next if (cut_line.size > macro_name.size) && restricted_separator_chars.include?(cut_line[macro_name.size]) # Skip if there is a character after the macro name, and the character is not permitted
|
162
|
-
next if (char_i > 0) && restricted_separator_chars.include?(line[char_i - 1]) # Skip if there is a character before the checking sequence, and the character is not permitted
|
163
|
-
|
164
|
-
# Here if the macro is 'accepted'
|
165
|
-
line = line[...char_i] + macro_def + line[(char_i + macro_name.size)..]
|
166
|
-
# Indicate that a macro was used
|
167
|
-
macro_used = true
|
168
|
-
end
|
169
|
-
|
170
|
-
if macro_used
|
171
|
-
# If a macro was used, indicate that on line level
|
172
|
-
line_macro_used = true
|
173
|
-
else
|
174
|
-
# If a macro wasn't used, proceed to the next character
|
175
|
-
char_i += 1
|
176
|
-
end
|
177
|
-
end # line characters loop
|
178
|
-
|
179
|
-
if line_macro_used
|
180
|
-
# If a macro was used on the current line, update the lines array, and add the line to check for macros again
|
181
|
-
lines[line_i] = line
|
182
|
-
new_lines_to_check << line_i
|
183
|
-
end
|
184
|
-
end # lines_to_check.each loop
|
185
|
-
|
186
|
-
lines_to_check = new_lines_to_check # Update lines to check with the new ones
|
187
|
-
|
188
|
-
end # while lines_to_check.size != 0 loop
|
189
25
|
|
190
|
-
|
191
|
-
end
|
192
|
-
|
193
|
-
|
194
|
-
def self.parse_code(lines)
|
26
|
+
extra_state = Hash.new
|
195
27
|
|
196
|
-
|
28
|
+
add_later_directives = []
|
197
29
|
|
198
|
-
|
199
|
-
|
200
|
-
lines.each_with_index do |line, line_i|
|
30
|
+
while line_i < lines.size
|
31
|
+
line = lines[line_i]
|
201
32
|
|
202
33
|
# Check if line is not just whitespace
|
203
|
-
is_char_whitespace = line.each_char.map{|c|
|
34
|
+
is_char_whitespace = line.each_char.map{|c| Kompiler::Config.whitespace_chars.include? c}
|
204
35
|
if !is_char_whitespace.include?(false) # If only whitespace
|
36
|
+
line_i += 1
|
205
37
|
next # Skip
|
206
38
|
end
|
207
39
|
|
208
|
-
#
|
209
|
-
# Label definitions are now a directive, so this isn't needed
|
210
|
-
#
|
211
|
-
|
212
|
-
# is_label, label_name = check_label(line)
|
213
|
-
# if is_label
|
214
|
-
# # labels[label_name] = instr_adr
|
215
|
-
# parsed_lines << {type: "label", label_name: label_name, label_address: instr_adr, address: instr_adr}
|
216
|
-
# next
|
217
|
-
# end
|
218
|
-
|
219
40
|
is_instruction, exec_instruction = Kompiler::Parsers.check_instruction(line)
|
220
41
|
if is_instruction
|
221
42
|
parsed_lines << {type: "instruction", instruction: exec_instruction[:instruction], operands: exec_instruction[:operands], address: instr_adr}
|
222
43
|
instr_adr += exec_instruction[:instruction][:bitsize] / 8
|
223
|
-
|
44
|
+
|
45
|
+
line_i += 1
|
46
|
+
|
224
47
|
next # Go to the next line
|
225
48
|
end
|
226
49
|
|
@@ -230,12 +53,59 @@ def self.parse_code(lines)
|
|
230
53
|
directive = directive_hash[:directive]
|
231
54
|
operands = directive_hash[:operands]
|
232
55
|
|
233
|
-
state = {current_address: instr_adr, parsed_lines: parsed_lines}
|
56
|
+
state = {current_address: instr_adr, parsed_lines: parsed_lines, lines: lines, line_i: line_i, extra_state: extra_state}
|
57
|
+
|
58
|
+
add_later_directive = false
|
59
|
+
|
60
|
+
if directive.keys.include?(:add_later_directive) && directive[:add_later_directive] == true
|
61
|
+
add_later_directive = true
|
62
|
+
end
|
63
|
+
|
64
|
+
new_operands = operands.map do |op|
|
65
|
+
if op[:type] == "run_block"
|
66
|
+
begin
|
67
|
+
block_state = state.dup
|
68
|
+
block_state[:block_args] = op[:block_args]
|
69
|
+
block_state[:labels] = self.get_labels(parsed_lines)
|
70
|
+
op = op[:block].call(block_state)
|
71
|
+
rescue
|
72
|
+
add_later_directive = true
|
73
|
+
op = {type: "immediate", value: 0, def_type: "kompiler_test_value", definition: "0"}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
op
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
if add_later_directive
|
81
|
+
|
82
|
+
try_state = {current_address: instr_adr, parsed_lines: parsed_lines.dup, lines: lines.dup, line_i: line_i, extra_state: extra_state.dup}
|
83
|
+
|
84
|
+
try_state = directive[:func].call(new_operands, try_state)
|
85
|
+
|
86
|
+
|
87
|
+
instr_adr = try_state[:current_address]
|
88
|
+
line_i = try_state[:line_i]
|
89
|
+
|
90
|
+
raise "Directive error 1.1" if !(lines == try_state[:lines])
|
91
|
+
raise "Directive error 1.2" if !(extra_state == try_state[:extra_state])
|
92
|
+
raise "Directive error 1.3" if !(parsed_lines == try_state[:parsed_lines][...parsed_lines.size]) # Check that the previous parsed lines were not changed by the directive
|
93
|
+
|
94
|
+
state.delete :parsed_lines
|
95
|
+
state.delete :lines
|
96
|
+
|
97
|
+
add_later_directives << {directive_hash: directive_hash, insert_i: parsed_lines.size, run_state: state, return_line_i: line_i, return_current_address: instr_adr}
|
98
|
+
|
99
|
+
next
|
100
|
+
end
|
234
101
|
|
235
|
-
state = directive[:func].call(
|
102
|
+
state = directive[:func].call(new_operands, state)
|
236
103
|
|
237
104
|
instr_adr = state[:current_address]
|
238
105
|
parsed_lines = state[:parsed_lines]
|
106
|
+
lines = state[:lines]
|
107
|
+
line_i = state[:line_i]
|
108
|
+
extra_state = state[:extra_state]
|
239
109
|
|
240
110
|
next # Skip to the next lime
|
241
111
|
end
|
@@ -246,9 +116,43 @@ def self.parse_code(lines)
|
|
246
116
|
|
247
117
|
raise "\"#{line}\" - Unknown syntax: Program build not possible"
|
248
118
|
|
119
|
+
end
|
120
|
+
|
121
|
+
add_later_directives.sort_by{|hash| hash[:insert_i]}.reverse.each do |add_later_directive|
|
122
|
+
directive_hash = add_later_directive[:directive_hash]
|
123
|
+
insert_i = add_later_directive[:insert_i]
|
124
|
+
state = add_later_directive[:run_state]
|
125
|
+
|
126
|
+
state[:parsed_lines] = parsed_lines[...insert_i]
|
127
|
+
state[:lines] = lines
|
128
|
+
|
129
|
+
directive = directive_hash[:directive]
|
130
|
+
operands = directive_hash[:operands]
|
131
|
+
|
132
|
+
new_operands = operands.map do |op|
|
133
|
+
if op[:type] == "run_block"
|
134
|
+
block_state = state.dup
|
135
|
+
block_state[:block_args] = op[:block_args]
|
136
|
+
block_state[:labels] = self.get_labels(parsed_lines)
|
137
|
+
op = op[:block].call(block_state)
|
138
|
+
end
|
139
|
+
op
|
140
|
+
end
|
141
|
+
|
142
|
+
state = directive[:func].call(new_operands, state)
|
143
|
+
|
144
|
+
|
145
|
+
raise "Directive error 2.1" if add_later_directive[:return_current_address] != state[:current_address]
|
146
|
+
raise "Directive error 2.2" if add_later_directive[:return_line_i] != state[:line_i]
|
147
|
+
|
148
|
+
|
149
|
+
parsed_lines = state[:parsed_lines] + parsed_lines[insert_i..]
|
249
150
|
end
|
250
|
-
|
251
|
-
|
151
|
+
|
152
|
+
|
153
|
+
state = {parsed_lines: parsed_lines, current_address: instr_adr, lines: lines, line_i: line_i, extra_state: extra_state}
|
154
|
+
|
155
|
+
return state
|
252
156
|
end
|
253
157
|
|
254
158
|
|
@@ -280,9 +184,21 @@ def self.construct_program_mc(parsed_lines, labels)
|
|
280
184
|
parsed_lines.each do |line|
|
281
185
|
case line[:type]
|
282
186
|
when "instruction"
|
283
|
-
program_state[:operands] = line[:operands]
|
284
187
|
program_state[:current_address] = line[:address]
|
285
188
|
|
189
|
+
operands = line[:operands]
|
190
|
+
operands.map! do |op|
|
191
|
+
if op[:type] == "run_block"
|
192
|
+
state = program_state.dup
|
193
|
+
state[:block_args] = op[:block_args]
|
194
|
+
op = op[:block].call(state)
|
195
|
+
end
|
196
|
+
op
|
197
|
+
end
|
198
|
+
|
199
|
+
program_state[:operands] = operands
|
200
|
+
|
201
|
+
|
286
202
|
mc_constructor = line[:instruction][:mc_constructor]
|
287
203
|
|
288
204
|
instr_bits = Kompiler::MachineCode_AST.build_mc(mc_constructor, program_state)
|
@@ -301,7 +217,7 @@ def self.construct_program_mc(parsed_lines, labels)
|
|
301
217
|
end
|
302
218
|
end
|
303
219
|
end
|
304
|
-
|
220
|
+
|
305
221
|
lines_bytes
|
306
222
|
end
|
307
223
|
|
@@ -347,23 +263,26 @@ def self.bit_lines_to_bytes(bit_lines)
|
|
347
263
|
end
|
348
264
|
|
349
265
|
|
350
|
-
def self.compile(code
|
266
|
+
def self.compile(code)
|
267
|
+
detailed_result = detailed_compile(code)
|
268
|
+
|
269
|
+
return detailed_result[:machine_code]
|
270
|
+
end
|
351
271
|
|
272
|
+
def self.detailed_compile(code)
|
352
273
|
lines = Kompiler::Parsers.get_code_lines(code)
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
parsed_lines =
|
359
|
-
|
360
|
-
labels = get_labels(parsed_lines)
|
361
|
-
|
362
|
-
# machine_code_bit_lines = construct_program_mc(parsed_lines, labels)
|
363
|
-
#
|
364
|
-
# machine_code_bytes = bit_lines_to_bytes(machine_code_bit_lines)
|
365
|
-
|
274
|
+
|
275
|
+
parsed_state = parse_code(lines)
|
276
|
+
|
277
|
+
# pp parsed_state
|
278
|
+
|
279
|
+
parsed_lines = parsed_state[:parsed_lines]
|
280
|
+
|
281
|
+
labels = get_labels(parsed_lines)
|
282
|
+
|
366
283
|
machine_code_bytes = construct_program_mc(parsed_lines, labels)
|
284
|
+
|
285
|
+
return {machine_code: machine_code_bytes, labels: labels, parsed_state: parsed_state}
|
367
286
|
end
|
368
287
|
|
369
288
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Contains config options for how Kompiler interprets different characters.
|
4
|
+
#
|
5
|
+
# Main config options:
|
6
|
+
# keyword_chars - list of characters that a keyword can contain
|
7
|
+
# label_chars - list of characters that a label name can contain
|
8
|
+
# whitespace_chars - list of characters that qualify as whitespace / separators of words
|
9
|
+
# string_delimiters - a list of characters that denote the start and end of a string
|
10
|
+
#
|
11
|
+
|
12
|
+
module Kompiler
|
13
|
+
|
14
|
+
module Config
|
15
|
+
|
16
|
+
@keyword_chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + ["_", "."]
|
17
|
+
@label_chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + ["_", "."]
|
18
|
+
@whitespace_chars = [" ", "\t", "\r"]
|
19
|
+
@string_delimiters = ['"', "'"]
|
20
|
+
|
21
|
+
# Returns the permittable keyword characters
|
22
|
+
def self.keyword_chars
|
23
|
+
@keyword_chars
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the permittable label characters
|
27
|
+
def self.label_chars
|
28
|
+
@label_chars
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.whitespace_chars
|
32
|
+
@whitespace_chars
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.string_delimiters
|
36
|
+
@string_delimiters
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|