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
data/lib/kompiler/directives.rb
CHANGED
@@ -1,6 +1,19 @@
|
|
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 all directives available in programs.
|
6
|
+
#
|
7
|
+
# Kompiler::Directives.directives is a list of all available directives.
|
8
|
+
#
|
9
|
+
# An entry's structure is:
|
10
|
+
# keyword - string or list of strings specifying the keywords by which the directive can be accessed.
|
11
|
+
# func - a Ruby lambda that receives the call operands and current state as arguments, and outputs the new state.
|
12
|
+
# collect_operands - optional (default is true). Specifies whether the operands should be parsed before calling the :func key lambda.
|
13
|
+
# If false, operands will be a raw string containing the string after the keyword.
|
14
|
+
#
|
15
|
+
|
16
|
+
|
4
17
|
module Kompiler
|
5
18
|
|
6
19
|
module Directives
|
@@ -14,9 +27,12 @@ end
|
|
14
27
|
keyword: "zeros",
|
15
28
|
func: lambda do |operands, state|
|
16
29
|
raise "Incorrect use of the \"zeros\" directive." if operands.size > 1 || (operands[0] && operands[0][:type] != "immediate")
|
30
|
+
|
17
31
|
n_zeros = operands[0][:value]
|
18
32
|
state[:current_address] += n_zeros
|
19
33
|
state[:parsed_lines] << {type: "insert", bits: (n_zeros*8).times.map{0} }
|
34
|
+
state[:line_i] += 1
|
35
|
+
|
20
36
|
state
|
21
37
|
end
|
22
38
|
},
|
@@ -30,6 +46,7 @@ end
|
|
30
46
|
|
31
47
|
state[:parsed_lines] << {type: "insert", bits: insert_bits, address: state[:current_address]}
|
32
48
|
state[:current_address] += insert_bytes.size
|
49
|
+
state[:line_i] += 1
|
33
50
|
|
34
51
|
state
|
35
52
|
end
|
@@ -39,6 +56,8 @@ end
|
|
39
56
|
func: lambda do |operands, state|
|
40
57
|
raise "Incorrect use of the \"align\" directive." if operands.size > 1 || (operands[0] && operands[0][:type] != "immediate")
|
41
58
|
|
59
|
+
state[:line_i] += 1
|
60
|
+
|
42
61
|
alignment = operands[0][:value]
|
43
62
|
to_add = alignment - (state[:current_address] % alignment)
|
44
63
|
|
@@ -72,6 +91,7 @@ end
|
|
72
91
|
|
73
92
|
# Add the label definition
|
74
93
|
state[:parsed_lines] << {type: "label", label_name: label_name, label_address: label_value, address: state[:current_address]}
|
94
|
+
state[:line_i] += 1
|
75
95
|
|
76
96
|
state
|
77
97
|
end
|
@@ -89,6 +109,7 @@ end
|
|
89
109
|
# Insert 64 bits of the value into the program
|
90
110
|
state[:current_address] += 8
|
91
111
|
state[:parsed_lines] << {type: "insert", bits: value_bits}
|
112
|
+
state[:line_i] += 1
|
92
113
|
|
93
114
|
state
|
94
115
|
end
|
@@ -106,6 +127,7 @@ end
|
|
106
127
|
# Insert 64 bits of the value into the program
|
107
128
|
state[:current_address] += 4
|
108
129
|
state[:parsed_lines] << {type: "insert", bits: value_bits}
|
130
|
+
state[:line_i] += 1
|
109
131
|
|
110
132
|
state
|
111
133
|
end
|
@@ -113,8 +135,8 @@ end
|
|
113
135
|
{
|
114
136
|
keyword: "bytes",
|
115
137
|
func: lambda do |operands, state|
|
138
|
+
raise "Incorrect use of the \"bytes\" directive." if (operands.size != 2) || (operands[0][:type] != "immediate" || operands[1][:type] != "immediate")
|
116
139
|
|
117
|
-
raise "Incorrect use of the \"bytes\" directive." if (operands.size != 2) || (operands[0][:type] != "immediate" && operands[1][:type] != "immediate")
|
118
140
|
|
119
141
|
n_bytes = operands[0][:value]
|
120
142
|
value = operands[1][:value]
|
@@ -124,6 +146,7 @@ end
|
|
124
146
|
# Insert the input amount of bytes of the value into the program
|
125
147
|
state[:current_address] += n_bytes
|
126
148
|
state[:parsed_lines] << {type: "insert", bits: value_bits}
|
149
|
+
state[:line_i] += 1
|
127
150
|
|
128
151
|
state
|
129
152
|
end
|
@@ -132,11 +155,12 @@ end
|
|
132
155
|
keyword: "set_pc",
|
133
156
|
func: lambda do |operands, state|
|
134
157
|
|
135
|
-
raise "Incorrect use of the \"set_pc\" directive." if (operands.size != 1) || (operands[0][:type] != "immediate")
|
158
|
+
raise "Incorrect use of the \"set_pc\" directive." if (operands.size != 1) || (operands[0][:type] != "immediate")
|
136
159
|
|
137
160
|
new_pc = operands[0][:value]
|
138
|
-
|
161
|
+
|
139
162
|
state[:current_address] = new_pc
|
163
|
+
state[:line_i] += 1
|
140
164
|
|
141
165
|
state
|
142
166
|
end
|
@@ -155,7 +179,346 @@ end
|
|
155
179
|
|
156
180
|
state[:current_address] += file_content.bytes.size
|
157
181
|
state[:parsed_lines] << {type: "insert", byte_string: file_content}
|
182
|
+
|
183
|
+
state[:line_i] += 1
|
184
|
+
|
185
|
+
state
|
186
|
+
end
|
187
|
+
},
|
188
|
+
{
|
189
|
+
keyword: ["load_end", "include_end"],
|
190
|
+
func: lambda do |operands, state|
|
191
|
+
|
192
|
+
raise "Incorrect use of the \"load_end\" directive" if (operands.size != 1) || (operands[0][:type] != "string")
|
193
|
+
|
194
|
+
file_selector = operands[0][:string]
|
195
|
+
|
196
|
+
files_to_load = Dir[file_selector]
|
197
|
+
|
198
|
+
# Create the loaded_files state entry if it doesn't exist
|
199
|
+
state[:extra_state][:loaded_files] = Array.new if !state[:extra_state].keys.include?(:loaded_files)
|
200
|
+
|
201
|
+
# Select only files that haven't been previously loaded
|
202
|
+
files_to_load.select!{|file_name| !state[:extra_state][:loaded_files].include?(file_name)}
|
203
|
+
|
204
|
+
# Add the files that will be loaded to the state entry
|
205
|
+
state[:extra_state][:loaded_files] += files_to_load
|
206
|
+
|
207
|
+
files_to_load.each do |load_filename|
|
208
|
+
file_content = File.read load_filename
|
209
|
+
|
210
|
+
file_lines = Kompiler::Parsers.get_code_lines(file_content)
|
211
|
+
|
212
|
+
state[:lines] += file_lines
|
213
|
+
end
|
214
|
+
|
215
|
+
state[:line_i] += 1
|
216
|
+
|
217
|
+
state
|
218
|
+
end
|
219
|
+
},
|
220
|
+
{
|
221
|
+
keyword: ["load", "include"],
|
222
|
+
func: lambda do |operands, state|
|
223
|
+
|
224
|
+
raise "Incorrect use of the \"load\" directive" if (operands.size != 1) || (operands[0][:type] != "string")
|
225
|
+
|
226
|
+
file_selector = operands[0][:string]
|
227
|
+
|
228
|
+
files_to_load = Dir[file_selector]
|
229
|
+
|
230
|
+
# Create the loaded_files state entry if it doesn't exist
|
231
|
+
state[:extra_state][:loaded_files] = Array.new if !state[:extra_state].keys.include?(:loaded_files)
|
232
|
+
|
233
|
+
# Select only files that haven't been previously loaded
|
234
|
+
files_to_load.select!{|file_name| !state[:extra_state][:loaded_files].include?(file_name)}
|
235
|
+
|
236
|
+
# Add the files that will be loaded to the state entry
|
237
|
+
state[:extra_state][:loaded_files] += files_to_load
|
238
|
+
|
239
|
+
total_load_lines = []
|
240
|
+
|
241
|
+
files_to_load.each do |load_filename|
|
242
|
+
file_content = File.read load_filename
|
158
243
|
|
244
|
+
file_lines = Kompiler::Parsers.get_code_lines(file_content)
|
245
|
+
|
246
|
+
total_load_lines += file_lines
|
247
|
+
end
|
248
|
+
|
249
|
+
# Move to the next line
|
250
|
+
state[:line_i] += 1
|
251
|
+
|
252
|
+
# Insert the lines at the correct place
|
253
|
+
state[:lines] = state[:lines][0...state[:line_i]] + total_load_lines + state[:lines][state[:line_i]..]
|
254
|
+
|
255
|
+
state
|
256
|
+
end
|
257
|
+
},
|
258
|
+
{
|
259
|
+
keyword: "value",
|
260
|
+
collect_operands: false,
|
261
|
+
func: lambda do |_, state|
|
262
|
+
_, operands = Kompiler::Parsers.extract_instruction_parts(state[:lines][state[:line_i]])
|
263
|
+
|
264
|
+
raise "Incorrect use of the .value directive - expected 2 operands: Program build not possible" if operands.size != 2
|
265
|
+
|
266
|
+
value_name = operands[0]
|
267
|
+
|
268
|
+
# Check that the name is made out of allowed characters
|
269
|
+
if value_name.each_char.map{|char| Kompiler::Config.keyword_chars.include?(char)}.include?(false)
|
270
|
+
raise "Incorrect use of the .value directive - the value name must contain only keyword characters: Program build not possible"
|
271
|
+
end
|
272
|
+
|
273
|
+
value_def = operands[1]
|
274
|
+
|
275
|
+
scan_lines = state[:lines][(state[:line_i] + 1)..]
|
276
|
+
|
277
|
+
scan_lines.each_with_index do |line, line_i|
|
278
|
+
|
279
|
+
start_i = 0
|
280
|
+
|
281
|
+
# Loop through each character starting position
|
282
|
+
while start_i < line.size
|
283
|
+
|
284
|
+
# Skip whitespace characters
|
285
|
+
if Kompiler::Config.whitespace_chars.include?(line[start_i])
|
286
|
+
start_i += 1
|
287
|
+
next
|
288
|
+
end
|
289
|
+
|
290
|
+
# Skip string definitions
|
291
|
+
if ['"', "'"].include? line[start_i]
|
292
|
+
str, parsed_length = Kompiler::Parsers.parse_str line[start_i..]
|
293
|
+
start_i += parsed_length
|
294
|
+
next
|
295
|
+
end
|
296
|
+
|
297
|
+
cut_line = line[start_i..]
|
298
|
+
|
299
|
+
value_word_found = false
|
300
|
+
|
301
|
+
# Check if the value name works
|
302
|
+
|
303
|
+
if cut_line.start_with?(value_name) # Check that the piece of text starts with the value name
|
304
|
+
if !Kompiler::Config.keyword_chars.include?(cut_line[value_name.size]) # Check that the cut text is a full word. This will not fire when value_name='arg', but the cut text is 'arg1'
|
305
|
+
value_word_found = true # Indicate that a replacement was found
|
306
|
+
|
307
|
+
scan_lines[line_i] = scan_lines[line_i][...start_i] + value_def + (scan_lines[line_i][(start_i+value_name.size)..] || "")
|
308
|
+
line = scan_lines[line_i]
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# Check if the value name wasn't detected
|
313
|
+
# If not, skip the text until the next whitespace character
|
314
|
+
if !value_word_found
|
315
|
+
while start_i < line.size && Kompiler::Config.keyword_chars.include?(line[start_i])
|
316
|
+
start_i += 1
|
317
|
+
end
|
318
|
+
start_i += 1 # Move one more character
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|
322
|
+
|
323
|
+
end
|
324
|
+
|
325
|
+
|
326
|
+
state[:extra_state][:values] = Array.new if !state[:extra_state].keys.include?(:values)
|
327
|
+
|
328
|
+
state[:extra_state][:values] << {name: value_name, def_value: value_def}
|
329
|
+
|
330
|
+
|
331
|
+
state[:lines] = state[:lines][..state[:line_i]] + scan_lines
|
332
|
+
|
333
|
+
state[:line_i] += 1
|
334
|
+
|
335
|
+
state
|
336
|
+
end
|
337
|
+
},
|
338
|
+
{
|
339
|
+
keyword: "macro",
|
340
|
+
collect_operands: false,
|
341
|
+
func: lambda do |_, state|
|
342
|
+
line_i = state[:line_i]
|
343
|
+
|
344
|
+
def_line = state[:lines][line_i]
|
345
|
+
|
346
|
+
# First: collect the part after ".macro"
|
347
|
+
|
348
|
+
char_i = 0
|
349
|
+
# Skip the whitespace before .macro
|
350
|
+
while char_i < def_line.size && Kompiler::Config.whitespace_chars.include?(def_line[char_i])
|
351
|
+
char_i += 1
|
352
|
+
end
|
353
|
+
# Skip the .macro
|
354
|
+
while char_i < def_line.size && Kompiler::Config.keyword_chars.include?(def_line[char_i])
|
355
|
+
char_i += 1
|
356
|
+
end
|
357
|
+
# Skip the whitespace after .macro
|
358
|
+
while char_i < def_line.size && Kompiler::Config.whitespace_chars.include?(def_line[char_i])
|
359
|
+
char_i += 1
|
360
|
+
end
|
361
|
+
|
362
|
+
# If the end of the line was reached, throw an error
|
363
|
+
raise "Incorrect .macro definition" if char_i == def_line.size
|
364
|
+
|
365
|
+
# Now char_i contains the first index of the text after .macro
|
366
|
+
|
367
|
+
macro_def = def_line[char_i..]
|
368
|
+
|
369
|
+
# Second: extract the macro's name
|
370
|
+
|
371
|
+
macro_name = ""
|
372
|
+
|
373
|
+
while char_i < def_line.size && Kompiler::Config.keyword_chars.include?(def_line[char_i])
|
374
|
+
macro_name << def_line[char_i]
|
375
|
+
char_i += 1
|
376
|
+
end
|
377
|
+
|
378
|
+
# Third: extract the operand names (code taken from parse_instruction_line in parsers.rb)
|
379
|
+
|
380
|
+
arg_names = Kompiler::Parsers.extract_instruction_operands(def_line[char_i..])
|
381
|
+
|
382
|
+
# Make sure that the arg names are unique
|
383
|
+
raise "Macro definition error - arguments cannot have the same name: Program build not possible" if arg_names.size != arg_names.uniq.size
|
384
|
+
|
385
|
+
# Extract the macro inside definition
|
386
|
+
|
387
|
+
line_i = state[:line_i] + 1
|
388
|
+
def_lines = []
|
389
|
+
|
390
|
+
whitespace_regexp = /[#{Kompiler::Config.whitespace_chars.join("|")}]*/
|
391
|
+
|
392
|
+
endmacro_regexp = /\A#{whitespace_regexp}\.?endmacro#{whitespace_regexp}\z/
|
393
|
+
|
394
|
+
while line_i < state[:lines].size
|
395
|
+
break if state[:lines][line_i].match? endmacro_regexp # Check if it's an end macro instruction
|
396
|
+
def_lines << state[:lines][line_i]
|
397
|
+
line_i += 1
|
398
|
+
end
|
399
|
+
|
400
|
+
|
401
|
+
# Find insert indexes for each argument
|
402
|
+
arg_insert_locations = arg_names.map{|arg_name| [arg_name, []]}.to_h
|
403
|
+
|
404
|
+
def_lines.each_with_index do |line, line_i|
|
405
|
+
|
406
|
+
start_i = 0
|
407
|
+
|
408
|
+
# Loop through each character starting position
|
409
|
+
while start_i < line.size
|
410
|
+
|
411
|
+
# Skip whitespace characters
|
412
|
+
if Kompiler::Config.whitespace_chars.include?(line[start_i])
|
413
|
+
start_i += 1
|
414
|
+
next
|
415
|
+
end
|
416
|
+
|
417
|
+
# Skip string definitions
|
418
|
+
if ['"', "'"].include? line[start_i]
|
419
|
+
str, parsed_length = Kompiler::Parsers.parse_str line[start_i..]
|
420
|
+
start_i += parsed_length
|
421
|
+
next
|
422
|
+
end
|
423
|
+
|
424
|
+
cut_line = line[start_i..]
|
425
|
+
|
426
|
+
arg_found = false
|
427
|
+
|
428
|
+
# Check if one of the argument names works
|
429
|
+
arg_names.each do |arg_name|
|
430
|
+
next if !cut_line.start_with?(arg_name) # Skip the argument if the line doesn't begin with it
|
431
|
+
next if Kompiler::Config.keyword_chars.include?(cut_line[arg_name.size]) # Skip if the argument is a partial word. So, for the argument 'arg', this will skip in the case of 'arg1'
|
432
|
+
# Here if the argument name should be replaced with the contents
|
433
|
+
arg_found = true # Indicate that a replacement was found
|
434
|
+
|
435
|
+
arg_insert_locations[arg_name] << [line_i, start_i] # Add the insert location to the list
|
436
|
+
|
437
|
+
# start_i += arg_name.size
|
438
|
+
def_lines[line_i] = def_lines[line_i][...start_i] + (def_lines[line_i][(start_i+arg_name.size)..] || "")
|
439
|
+
line = def_lines[line_i]
|
440
|
+
|
441
|
+
break # Skip the arguments loop
|
442
|
+
end
|
443
|
+
|
444
|
+
# Check if an argument was found
|
445
|
+
# If not, skip the text until the next whitespace character
|
446
|
+
if !arg_found
|
447
|
+
while start_i < line.size && Kompiler::Config.keyword_chars.include?(line[start_i])
|
448
|
+
start_i += 1
|
449
|
+
end
|
450
|
+
start_i += 1 # Move one more character
|
451
|
+
end
|
452
|
+
|
453
|
+
end
|
454
|
+
|
455
|
+
end
|
456
|
+
|
457
|
+
state[:extra_state][:macros] = Array.new if !state[:extra_state].keys.include?(:macros)
|
458
|
+
|
459
|
+
state[:extra_state][:macros] << {name: macro_name, args: arg_names, def_lines: def_lines, arg_insert_locations: arg_insert_locations}
|
460
|
+
|
461
|
+
|
462
|
+
# Scan the lines after the macro for the macro call and replace it with the macro definition
|
463
|
+
|
464
|
+
scan_lines = state[:lines][(state[:line_i] + def_lines.size + 1 + 1)..]
|
465
|
+
|
466
|
+
line_i = 0
|
467
|
+
|
468
|
+
# Re-group argument insert locations by line -> [index, arg index]
|
469
|
+
|
470
|
+
arg_insert_locations_regrouped = def_lines.size.times.map{[]}
|
471
|
+
|
472
|
+
arg_insert_locations.each do |arg_name, insert_locations|
|
473
|
+
insert_locations.each do |line_i, char_i|
|
474
|
+
arg_insert_locations_regrouped[line_i] << [char_i, arg_names.index(arg_name)]
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
|
479
|
+
while line_i < scan_lines.size
|
480
|
+
keyword, operands = Kompiler::Parsers.extract_instruction_parts(scan_lines[line_i])
|
481
|
+
|
482
|
+
# If parsing failed, move on to the next line
|
483
|
+
if keyword == false
|
484
|
+
line_i += 1
|
485
|
+
next
|
486
|
+
end
|
487
|
+
|
488
|
+
# If the keyword isn't the macro's name, skip the line
|
489
|
+
if keyword != macro_name
|
490
|
+
line_i += 1
|
491
|
+
next
|
492
|
+
end
|
493
|
+
|
494
|
+
# Here when the keyword matches the macro name
|
495
|
+
|
496
|
+
# Check that the number of operands is correct
|
497
|
+
if operands.size != arg_names.size
|
498
|
+
raise "Incorrect use of the \"#{macro_name}\" macro - #{arg_names.size} operands expected, but #{operands.size} were given: Program build not possible."
|
499
|
+
end
|
500
|
+
|
501
|
+
# Build the replacement lines for the macro call
|
502
|
+
build_lines = def_lines.map{|line| line.dup} # Copying strings inside array, because array.dup doesn't work for elements
|
503
|
+
|
504
|
+
arg_insert_locations_regrouped.each_with_index do |locations, line_i|
|
505
|
+
# Sort the locations by the insert character from largest to smallest, so that the inserts are made from end to start
|
506
|
+
locations.sort_by{|el| el[0]}.reverse.each do |char_i, arg_index|
|
507
|
+
build_lines[line_i].insert char_i, operands[arg_index]
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
# Replace the macro call with the built lines
|
512
|
+
scan_lines = scan_lines[...line_i] + build_lines + scan_lines[(line_i + 1)..]
|
513
|
+
|
514
|
+
# Skip the inserted macro
|
515
|
+
line_i += build_lines.size
|
516
|
+
end
|
517
|
+
|
518
|
+
state[:lines] = state[:lines][...state[:line_i]] + scan_lines
|
519
|
+
|
520
|
+
state[:line_i] += 1
|
521
|
+
|
159
522
|
state
|
160
523
|
end
|
161
524
|
}
|
@@ -164,4 +527,4 @@ end
|
|
164
527
|
|
165
528
|
end # Kompiler::Directives
|
166
529
|
|
167
|
-
end # Kompiler
|
530
|
+
end # Kompiler
|