Add my homegrown cross compiler scripts

This commit is contained in:
Eugen Wissner 2025-05-05 23:11:52 +02:00
parent df1c0486c5
commit 3bd86e6e1c
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
8 changed files with 1042 additions and 640 deletions

View File

@ -7,28 +7,12 @@ require 'open3'
require 'rake/clean' require 'rake/clean'
require 'term/ansicolor' require 'term/ansicolor'
CLOBBER.include 'build' CLEAN.include 'build/boot'
CROSS_GCC = '../riscv32-ilp32d--glibc/bin/riscv32-linux-gcc' directory 'build/boot'
SYSROOT = '../riscv32-ilp32d--glibc/riscv32-buildroot-linux-gnu/sysroot'
QEMU = 'qemu-riscv32'
def assemble_stage(output, compiler, source)
arguments = [QEMU, '-L', SYSROOT, *compiler]
puts Term::ANSIColor.green(arguments * ' ')
puts
Open3.popen2(*arguments) do |qemu_in, qemu_out|
qemu_in.write File.read(*source)
qemu_in.close
IO.copy_stream qemu_out, output
qemu_out.close
end
end
desc 'Final stage' desc 'Final stage'
task default: ['build/stage2b', 'build/stage2b.s', 'boot/stage2.elna'] do |t| task default: ['build/boot/stage2b', 'build/boot/stage2b.s', 'boot/stage2.elna'] do |t|
exe, previous_output, source = t.prerequisites exe, previous_output, source = t.prerequisites
cat_arguments = ['cat', source] cat_arguments = ['cat', source]
@ -36,40 +20,3 @@ task default: ['build/stage2b', 'build/stage2b.s', 'boot/stage2.elna'] do |t|
diff_arguments = ['diff', '-Nur', '--text', previous_output, '-'] diff_arguments = ['diff', '-Nur', '--text', previous_output, '-']
Open3.pipeline(cat_arguments, compiler_arguments, diff_arguments) Open3.pipeline(cat_arguments, compiler_arguments, diff_arguments)
end end
directory 'build'
Dir.glob('boot/*.s').each do |assembly_source|
target_object = Pathname.new('build') + Pathname.new(assembly_source).basename.sub_ext('.o')
file target_object.to_s => [assembly_source, 'build'] do |t|
sh CROSS_GCC, '-c', '-o', t.name, assembly_source
end
end
desc 'Initial stage'
file 'build/stage1' => ['build/tokenizer.o', 'build/stage1.o', 'build/common-boot.o'] do |t|
sh CROSS_GCC, '-nostdlib', '-o', t.name, *t.prerequisites
end
file 'build/stage2a.s' => ['build/stage1', 'boot/stage2.elna'] do |t|
source, exe = t.prerequisites.partition { |prerequisite| prerequisite.end_with? '.elna' }
File.open t.name, 'w' do |output|
assemble_stage output, exe, source
end
end
['build/stage2a', 'build/stage2b'].each do |exe|
file exe => [exe.ext('.s'), 'build/common-boot.o'] do |t|
sh CROSS_GCC, '-nostdlib', '-o', t.name, *t.prerequisites
end
end
file 'build/stage2b.s' => ['build/stage2a', 'boot/stage2.elna'] do |t|
source, exe = t.prerequisites.partition { |prerequisite| prerequisite.end_with? '.elna' }
File.open t.name, 'w' do |output|
assemble_stage output, exe, source
end
end

View File

@ -34,20 +34,21 @@
.equ TOKEN_IDENTIFIER, 27 .equ TOKEN_IDENTIFIER, 27
# The constant should match the character index in the byte_keywords string. # The constant should match the character index in the byte_keywords string.
.equ TOKEN_AND, 28 .equ TOKEN_AND, TOKEN_IDENTIFIER + 1
.equ TOKEN_DOT, 29 .equ TOKEN_DOT, TOKEN_IDENTIFIER + 2
.equ TOKEN_COMMA, 30 .equ TOKEN_COMMA, TOKEN_IDENTIFIER + 3
.equ TOKEN_COLON, 31 .equ TOKEN_COLON, TOKEN_IDENTIFIER + 4
.equ TOKEN_SEMICOLON, 32 .equ TOKEN_SEMICOLON, TOKEN_IDENTIFIER + 5
.equ TOKEN_LEFT_PAREN, 33 .equ TOKEN_LEFT_PAREN, TOKEN_IDENTIFIER + 6
.equ TOKEN_RIGHT_PAREN, 34 .equ TOKEN_RIGHT_PAREN, TOKEN_IDENTIFIER + 7
.equ TOKEN_LEFT_BRACKET, 35 .equ TOKEN_LEFT_BRACKET, TOKEN_IDENTIFIER + 8
.equ TOKEN_RIGHT_BRACKET, 36 .equ TOKEN_RIGHT_BRACKET, TOKEN_IDENTIFIER + 9
.equ TOKEN_HAT, 37 .equ TOKEN_HAT, TOKEN_IDENTIFIER + 10
.equ TOKEN_EQUALS, 38 .equ TOKEN_EQUALS, TOKEN_IDENTIFIER + 11
.equ TOKEN_PLUS, 39 .equ TOKEN_PLUS, TOKEN_IDENTIFIER + 12
.equ TOKEN_MINUS, 40 .equ TOKEN_MINUS, TOKEN_IDENTIFIER + 13
.equ TOKEN_ASTERISK, 41 .equ TOKEN_ASTERISK, TOKEN_IDENTIFIER + 14
.equ TOKEN_AT, 42 .equ TOKEN_AT, TOKEN_IDENTIFIER + 15
.equ TOKEN_ASSIGN, 43 .equ TOKEN_ASSIGN, 43
.equ TOKEN_INTEGER, 44

View File

@ -4,9 +4,29 @@
.global _start # Program entry point. .global _start # Program entry point.
#
# Registers used as global variables: # Registers used as global variables:
# s1 - Contains the current position in the source text. # s1 - Contains the current position in the source text.
# s2 - Label counter. # s2 - Label counter.
#
# - The compiler expects valid input, otherwise it will generate invalid
# assembly or hang. There is no error checking, no semantic analysis, no
# type checking.
#
# - Imports with only a module name without package, e.g.
# "import dummy", can be parsed, but are ignored.
#
# - No loops. Only labels and goto.
#
# - Only unsigned number literals are supported (in decimal or
# hexadecimal format).
#
# - Comments are accepted only at the end of a line.
#
# - Return can be used only as the last statement of a procedure. It
# doesn't actually return, but sets a0 to the appropriate value.
#
# - The lvalue of an assignment can only be an identifier.
.include "boot/definitions.inc" .include "boot/definitions.inc"
@ -73,9 +93,6 @@ _compile_import:
addi s0, sp, 24 addi s0, sp, 24
.Lcompile_import_loop: .Lcompile_import_loop:
call _skip_comment
call _skip_spaces
mv a0, s1 mv a0, s1
addi a1, sp, 0 addi a1, sp, 0
call _tokenize_next call _tokenize_next
@ -106,7 +123,6 @@ _build_binary_expression:
li a0, 0 li a0, 0
call _build_expression call _build_expression
call _skip_spaces
mv a0, s1 mv a0, s1
addi a1, sp, 12 addi a1, sp, 12
@ -114,26 +130,26 @@ _build_binary_expression:
lw t0, 12(sp) lw t0, 12(sp)
li t1, TOKEN_AND li t1, TOKEN_AND
beq t0, t1, .L_build_binary_expression_and beq t0, t1, .Lbuild_binary_expression_and
li t1, TOKEN_OR li t1, TOKEN_OR
beq t0, t1, .L_build_binary_expression_or beq t0, t1, .Lbuild_binary_expression_or
li t1, TOKEN_PLUS li t1, TOKEN_PLUS
beq t0, t1, .L_build_binary_expression_plus beq t0, t1, .Lbuild_binary_expression_plus
li t1, TOKEN_EQUALS li t1, TOKEN_EQUALS
beq t0, t1, .L_build_binary_expression_equal beq t0, t1, .Lbuild_binary_expression_equal
li t1, TOKEN_ASTERISK li t1, TOKEN_ASTERISK
beq t0, t1, .L_build_binary_expression_product beq t0, t1, .Lbuild_binary_expression_product
li t1, TOKEN_MINUS li t1, TOKEN_MINUS
beq t0, t1, .L_build_binary_expression_minus beq t0, t1, .Lbuild_binary_expression_minus
j .Lbuild_binary_expression_end j .Lbuild_binary_expression_end
.L_build_binary_expression_equal: .Lbuild_binary_expression_equal:
mv s1, a0 # Skip =. mv s1, a0 # Skip =.
li a0, 1 li a0, 1
call _build_expression call _build_expression
@ -147,7 +163,7 @@ _build_binary_expression:
j .Lbuild_binary_expression_end j .Lbuild_binary_expression_end
.L_build_binary_expression_and: .Lbuild_binary_expression_and:
mv s1, a0 # Skip &. mv s1, a0 # Skip &.
li a0, 1 li a0, 1
call _build_expression call _build_expression
@ -157,7 +173,7 @@ _build_binary_expression:
j .Lbuild_binary_expression_end j .Lbuild_binary_expression_end
.L_build_binary_expression_or: .Lbuild_binary_expression_or:
mv s1, a0 # Skip or. mv s1, a0 # Skip or.
li a0, 1 li a0, 1
call _build_expression call _build_expression
@ -167,7 +183,7 @@ _build_binary_expression:
j .Lbuild_binary_expression_end j .Lbuild_binary_expression_end
.L_build_binary_expression_plus: .Lbuild_binary_expression_plus:
mv s1, a0 # Skip +. mv s1, a0 # Skip +.
li a0, 1 li a0, 1
call _build_expression call _build_expression
@ -177,7 +193,7 @@ _build_binary_expression:
j .Lbuild_binary_expression_end j .Lbuild_binary_expression_end
.L_build_binary_expression_minus: .Lbuild_binary_expression_minus:
mv s1, a0 # Skip -. mv s1, a0 # Skip -.
li a0, 1 li a0, 1
call _build_expression call _build_expression
@ -187,7 +203,7 @@ _build_binary_expression:
j .Lbuild_binary_expression_end j .Lbuild_binary_expression_end
.L_build_binary_expression_product: .Lbuild_binary_expression_product:
mv s1, a0 # Skip *. mv s1, a0 # Skip *.
li a0, 1 li a0, 1
call _build_expression call _build_expression
@ -448,7 +464,6 @@ _build_expression:
call _write_out call _write_out
addi s1, s1, 1 # Skip @. addi s1, s1, 1 # Skip @.
call _skip_spaces
call _read_token call _read_token
sw s1, 32(sp) sw s1, 32(sp)
sw a0, 28(sp) sw a0, 28(sp)
@ -654,7 +669,6 @@ _compile_call:
sw zero, 12(sp) # Argument count for a procedure call. sw zero, 12(sp) # Argument count for a procedure call.
.Lcompile_call_paren: .Lcompile_call_paren:
call _skip_spaces
lbu t0, (s1) lbu t0, (s1)
li t1, 0x29 # ) li t1, 0x29 # )
beq t0, t1, .Lcompile_call_complete beq t0, t1, .Lcompile_call_complete
@ -688,7 +702,6 @@ _compile_call:
li a1, 5 li a1, 5
call _write_out call _write_out
call _skip_spaces
lbu t0, (s1) lbu t0, (s1)
li t1, ',' li t1, ','
bne t0, t1, .Lcompile_call_paren bne t0, t1, .Lcompile_call_paren
@ -726,7 +739,6 @@ _compile_call:
li a0, '\n' li a0, '\n'
call _put_char call _put_char
call _skip_spaces
addi s1, s1, 1 # Skip the close paren. addi s1, s1, 1 # Skip the close paren.
# Epilogue. # Epilogue.
@ -912,7 +924,6 @@ _compile_procedure_section:
.Lcompile_procedure_section_loop: .Lcompile_procedure_section_loop:
call _skip_spaces call _skip_spaces
call _skip_comment call _skip_comment
call _skip_spaces
mv a0, s1 mv a0, s1
addi a1, sp, 4 addi a1, sp, 4
@ -945,7 +956,6 @@ _compile_module_declaration:
call _write_out call _write_out
# Skip "program". # Skip "program".
call _skip_comment
mv a0, s1 mv a0, s1
addi a1, sp, 4 addi a1, sp, 4
call _tokenize_next call _tokenize_next
@ -965,9 +975,6 @@ _compile_constant_section:
sw s0, 16(sp) sw s0, 16(sp)
addi s0, sp, 24 addi s0, sp, 24
call _skip_comment
call _skip_spaces
mv a0, s1 mv a0, s1
addi a1, sp, 4 addi a1, sp, 4
call _tokenize_next call _tokenize_next
@ -999,25 +1006,22 @@ _compile_constant_section:
.type _compile_constant, @function .type _compile_constant, @function
_compile_constant: _compile_constant:
# Prologue. # Prologue.
addi sp, sp, -24 addi sp, sp, -32
sw ra, 20(sp) sw ra, 28(sp)
sw s0, 16(sp) sw s0, 24(sp)
addi s0, sp, 24 addi s0, sp, 32
mv a0, s1 mv a0, s1
addi a1, sp, 4 addi a1, sp, 12
call _tokenize_next call _tokenize_next
addi a1, sp, 0
sub a1, a0, s1 # The identifier end from _tokenize_next should be in a0. call _tokenize_next # Skip the assignment sign.
mv a0, s1 mv s1, a0
add s1, s1, a1 # Save the identifier pointer before advancing it. # Write identifier the identifier.
lw a0, 20(sp)
lw a1, 16(sp)
call _write_out call _write_out
mv a0, s1
addi a1, sp, 4
call _tokenize_next
mv s1, a0 # Skip the assignment sign.
# : .long # : .long
li t0, 0x20676e6f # ong_ li t0, 0x20676e6f # ong_
sw t0, 4(sp) sw t0, 4(sp)
@ -1027,21 +1031,23 @@ _compile_constant:
li a1, 8 li a1, 8
call _write_out call _write_out
call _skip_spaces mv a0, s1
call _read_token addi a1, sp, 12
call _tokenize_next
mv s1, a0
mv a1, a0 # The literal length from _read_token should be in a1. lw a0, 20(sp) # Save the literal pointer before advancing it.
mv a0, s1 # Save the literal pointer before advancing it. lw a1, 16(sp) # The literal length.
add s1, s1, a1
call _write_out call _write_out
li a0, '\n' li a0, '\n'
call _put_char call _put_char
call _skip_spaces
# Epilogue. # Epilogue.
lw ra, 20(sp) lw ra, 28(sp)
lw s0, 16(sp) lw s0, 24(sp)
addi sp, sp, 24 addi sp, sp, 32
ret ret
.type _compile_variable_section, @function .type _compile_variable_section, @function
@ -1080,6 +1086,7 @@ _compile_variable_section:
addi sp, sp, 24 addi sp, sp, 24
ret ret
# Compile a global variable.
.type _compile_variable, @function .type _compile_variable, @function
_compile_variable: _compile_variable:
# Prologue. # Prologue.
@ -1088,32 +1095,33 @@ _compile_variable:
sw s0, 40(sp) sw s0, 40(sp)
addi s0, sp, 48 addi s0, sp, 48
call _read_token
# Save the identifier on the stack since it should emitted multiple times. # Save the identifier on the stack since it should emitted multiple times.
sw s1, 36(sp) mv a0, s1
sw a0, 32(sp) addi a1, sp, 28
add s1, s1, a0 call _tokenize_next
addi a1, sp, 4
call _tokenize_next # Skip the colon in front of the type.
addi a1, sp, 4
call _tokenize_next # Skip the opening bracket.
addi a1, sp, 16
call _tokenize_next # Save the array size on the stack since it has to be emitted multiple times.
addi a1, sp, 4
call _tokenize_next # Skip the closing bracket.
addi a1, sp, 4
call _tokenize_next # Skip the type.
mv s1, a0
call _skip_spaces /* DEBUG
addi s1, s1, 1 # Skip the colon in front of the type. lw a0, 24(sp)
add a0, a0, '0'
call _skip_spaces
addi s1, s1, 1 # Skip the opening bracket.
call _read_token
# Save the array size on the stack since it has to be emitted multiple times.
sw s1, 28(sp)
sw a0, 24(sp) sw a0, 24(sp)
add s1, s1, a0 addi a0, sp, 24
li a1, 1
call _skip_spaces call _write_error
addi s1, s1, 1 # Skip the closing bracket. lw a0, 28(sp)
li a1, 8
call _skip_spaces call _write_error
call _read_token */
add s1, s1, a0 # Skip the type.
# .type identifier, @object # .type identifier, @object
la a0, asm_type la a0, asm_type
@ -1134,15 +1142,15 @@ _compile_variable:
call _write_out call _write_out
li t0, 0x206f7265 # ero_ li t0, 0x206f7265 # ero_
sw t0, 20(sp) sw t0, 12(sp)
li t0, 0x7a2e203a # : .z li t0, 0x7a2e203a # : .z
sw t0, 16(sp) sw t0, 8(sp)
addi a0, sp, 16 addi a0, sp, 8
li a1, 8 li a1, 8
call _write_out call _write_out
lw a0, 28(sp) lw a0, 24(sp)
lw a1, 24(sp) lw a1, 20(sp)
call _write_out call _write_out
li a0, '\n' li a0, '\n'
@ -1215,9 +1223,6 @@ _compile_procedure:
# Generate the body of the procedure. # Generate the body of the procedure.
.Lcompile_procedure_body: .Lcompile_procedure_body:
call _skip_spaces
call _read_line
sw a0, 12(sp)
li t0, 0x0a646e65 # end\n li t0, 0x0a646e65 # end\n
sw t0, 8(sp) sw t0, 8(sp)
mv a0, s1 mv a0, s1
@ -1227,7 +1232,6 @@ _compile_procedure:
beqz a0, .Lcompile_procedure_end beqz a0, .Lcompile_procedure_end
lw a0, 12(sp)
call _compile_statement call _compile_statement
j .Lcompile_procedure_body j .Lcompile_procedure_body
@ -1245,111 +1249,95 @@ _compile_procedure:
addi sp, sp, 32 addi sp, sp, 32
ret ret
# Compiles a goto statement to an uncoditional jump.
.type _compile_goto, @function .type _compile_goto, @function
_compile_goto: _compile_goto:
# Prologue. # Prologue.
addi sp, sp, -16 addi sp, sp, -32
sw ra, 12(sp) sw ra, 28(sp)
sw s0, 8(sp) sw s0, 24(sp)
addi s0, sp, 16 addi s0, sp, 32
addi s1, s1, 4 # Skip the goto keyword. mv a0, s1
addi a1, sp, 0
call _tokenize_next # Skip the goto keyword.
addi a1, sp, 0
call _tokenize_next # We should be on dot the label is beginning with.
addi a1, sp, 0
call _tokenize_next# Save the label name.
mv s1, a0
li t0, 0x206a # j_ li t0, 0x2e206a # j .
sw t0, 8(sp) sw t0, 12(sp)
addi a0, sp, 8 addi a0, sp, 12
li a1, 2 li a1, 3
call _write_out call _write_out
call _skip_spaces
sw s1, 8(sp) # We should be on dot the label is beginning with.
addi s1, s1, 1
call _read_token
add s1, s1, a0
addi a1, a0, 1 # Label length and the dot.
lw a0, 8(sp) # Saved dot position. lw a0, 8(sp) # Saved dot position.
lw a1, 4(sp)
call _write_out call _write_out
addi s1, s1, 1 # Skip the new line.
li a0, '\n' li a0, '\n'
call _put_char call _put_char
# Epilogue. # Epilogue.
lw ra, 12(sp) lw ra, 28(sp)
lw s0, 8(sp) lw s0, 24(sp)
addi sp, sp, 16 addi sp, sp, 32
ret ret
# a0 - Line length. # Rewrites a label to assembly.
.type _compile_label, @function .type _compile_label, @function
_compile_label: _compile_label:
# Prologue. # Prologue.
addi sp, sp, -16 addi sp, sp, -32
sw ra, 12(sp) sw ra, 28(sp)
sw s0, 8(sp) sw s0, 24(sp)
addi s0, sp, 16 addi s0, sp, 32
sw a0, 0(sp) # Save the line length.
mv a1, a0 # Argument for _write_out later.
lw t0, 0(sp) # Line length.
mv t1, s1 # Line start.
add t1, t1, t0
addi t1, t1, -1 # Last character on the line.
lbu t1, (t1)
li t2, ';'
bne t1, t2, .Lcompile_label_colon
addi a1, a1, -1
.Lcompile_label_colon:
# Write the whole line as is.
mv a0, s1 mv a0, s1
call _write_out addi a1, sp, 8
call _tokenize_next # Dot starting the label.
addi a1, sp, 8
call _tokenize_next
mv s1, a0
li t0, 0x3a # : li a0, '.'
sw t0, 4(sp) call _put_char
addi a0, sp, 4 lw a0, 16(sp)
li a1, 1 lw a1, 12(sp)
call _write_out call _write_out
li a0, ':'
li t0, '\n' call _put_char
sw t0, 4(sp) li a0, '\n'
addi a0, sp, 4 call _put_char
li a1, 1
call _write_out
lw a0, 0(sp)
addi a0, a0, 1 # Skip the new line as well.
add s1, s1, a0 # Skip the line.
# Epilogue. # Epilogue.
lw ra, 12(sp) lw ra, 28(sp)
lw s0, 8(sp) lw s0, 24(sp)
addi sp, sp, 16 addi sp, sp, 32
ret ret
# Just skips the return keyword and evaluates the return expression.
.type _compile_return, @function .type _compile_return, @function
_compile_return: _compile_return:
# Prologue. # Prologue.
addi sp, sp, -16 addi sp, sp, -32
sw ra, 12(sp) sw ra, 28(sp)
sw s0, 8(sp) sw s0, 24(sp)
addi s0, sp, 16 addi s0, sp, 32
addi s1, s1, 6 # Skip return. mv a0, s1
call _skip_spaces addi a1, sp, 12
call _tokenize_next
mv s1, a0 # Skip return.
call _build_binary_expression call _build_binary_expression
# Epilogue. # Epilogue.
lw ra, 12(sp) lw ra, 28(sp)
lw s0, 8(sp) lw s0, 24(sp)
addi sp, sp, 16 addi sp, sp, 32
ret ret
.type _compile_if, @function .type _compile_if, @function
@ -1403,8 +1391,6 @@ _compile_if:
li t1, TOKEN_END li t1, TOKEN_END
beq t0, t1, .Lcompile_if_end beq t0, t1, .Lcompile_if_end
call _read_line
li a1, 1
call _compile_statement call _compile_statement
j .Lcompile_if_loop j .Lcompile_if_loop
@ -1422,7 +1408,7 @@ _compile_if:
call _printi call _printi
# Finalize the label. # Finalize the label.
li t0, 0x0a3a # :\n:\n li t0, 0x0a3a # :\n
sh t0, 16(sp) sh t0, 16(sp)
addi a0, sp, 16 addi a0, sp, 16
li a1, 2 li a1, 2
@ -1436,11 +1422,7 @@ _compile_if:
addi sp, sp, 32 addi sp, sp, 32
ret ret
# Parameters: # Checks for the type of the current statement and compiles it.
# a0 - Line length.
#
# Returns 1 in a0 if the parsed line contained a text section element such a
# procedure or the program entry point. Otherwise sets a0 to 0.
.type _compile_statement, @function .type _compile_statement, @function
_compile_statement: _compile_statement:
# Prologue. # Prologue.
@ -1449,42 +1431,17 @@ _compile_statement:
sw s0, 24(sp) sw s0, 24(sp)
addi s0, sp, 32 addi s0, sp, 32
# Preserve passed arguments.
sw a0, 20(sp)
mv a0, s1
lw a1, 20(sp)
call _is_local_identifier
bnez a0, .Lcompile_statement_identifier
mv a0, s1
li a1, 2
call _is_register_identifier
bnez a0, .Lcompile_statement_identifier
li t0, 0x6f746f67 # goto
sw t0, 12(sp)
mv a0, s1
addi a1, sp, 12
li a2, 4
call _memcmp
beqz a0, .Lcompile_statement_goto
call _skip_comment
/* DEBUG
mv a0, s1
li a1, 4
call _write_error
mv a0, s1
li a1, 4
call _write_error
*/
mv a0, s1 mv a0, s1
addi a1, sp, 0 addi a1, sp, 0
call _tokenize_next call _tokenize_next
lw t0, 0(sp) lw t0, 0(sp)
li t1, TOKEN_IDENTIFIER
beq t0, t1, .Lcompile_statement_identifier
li t1, TOKEN_GOTO
beq t0, t1, .Lcompile_statement_goto
li t1, TOKEN_RETURN li t1, TOKEN_RETURN
beq t0, t1, .Lcompile_statement_return beq t0, t1, .Lcompile_statement_return
@ -1494,10 +1451,6 @@ _compile_statement:
li t1, TOKEN_DOT li t1, TOKEN_DOT
beq t0, t1, .Lcompile_statement_label beq t0, t1, .Lcompile_statement_label
lbu t0, (s1)
li t1, '_'
beq t0, t1, .Lcompile_statement_identifier
j .Lcompile_statement_empty # Else. j .Lcompile_statement_empty # Else.
.Lcompile_statement_if: .Lcompile_statement_if:
@ -1505,7 +1458,6 @@ _compile_statement:
j .Lcompile_statement_end j .Lcompile_statement_end
.Lcompile_statement_label: .Lcompile_statement_label:
lw a0, 20(sp)
call _compile_label call _compile_label
j .Lcompile_statement_end j .Lcompile_statement_end
@ -1527,7 +1479,6 @@ _compile_statement:
.Lcompile_statement_end: .Lcompile_statement_end:
sw a0, 12(sp) sw a0, 12(sp)
call _skip_spaces
call _skip_comment call _skip_comment
lw a0, 12(sp) lw a0, 12(sp)
@ -1559,10 +1510,10 @@ _compile_text_section:
.type _compile_entry_point, @function .type _compile_entry_point, @function
_compile_entry_point: _compile_entry_point:
# Prologue. # Prologue.
addi sp, sp, -8 addi sp, sp, -32
sw ra, 4(sp) sw ra, 28(sp)
sw s0, 0(sp) sw s0, 24(sp)
addi s0, sp, 8 addi s0, sp, 32
# .type _start, @function # .type _start, @function
la a0, asm_start la a0, asm_start
@ -1581,7 +1532,6 @@ _compile_entry_point:
li t1, TOKEN_END li t1, TOKEN_END
beq t0, t1, .Lcompile_entry_point_end beq t0, t1, .Lcompile_entry_point_end
lw a0, 12(sp)
call _compile_statement call _compile_statement
j .Lcompile_entry_point_body j .Lcompile_entry_point_body
@ -1593,27 +1543,9 @@ _compile_entry_point:
call _write_out call _write_out
# Epilogue. # Epilogue.
lw ra, 4(sp) lw ra, 28(sp)
lw s0, 0(sp) lw s0, 24(sp)
addi sp, sp, 8 addi sp, sp, 32
ret
# Finds the end of the line and returns its length in a0.
.type _read_line, @function
_read_line:
mv t0, s1 # Local position in the source text.
.Lread_line_do:
lbu t1, (t0) # t1 = Current character.
beqz t1, .Lread_line_end # Exit the loop on the NUL character.
li t2, '\n'
beq t1, t2, .Lread_line_end # Exit the loop on the new line.
addi t0, t0, 1
j .Lread_line_do
.Lread_line_end:
sub a0, t0, s1 # Return the line length.
ret ret
.type _compile, @function .type _compile, @function

File diff suppressed because it is too large Load Diff

View File

@ -266,9 +266,9 @@ transitions:
.word 0x05ff, 0x0102, 0x05ff, 0x0102, 0x0102, 0x0102, 0x05ff, 0x05ff .word 0x05ff, 0x0102, 0x05ff, 0x0102, 0x0102, 0x0102, 0x05ff, 0x05ff
.word 0x05ff, 0x05ff, 0x05ff, 0x05ff # 0x02 Identifier .word 0x05ff, 0x05ff, 0x05ff, 0x05ff # 0x02 Identifier
.word 0x02ff, 0x0103, 0x00ff, 0x02ff, 0x02ff, 0x02ff, 0x02ff, 0x02ff .word 0x08ff, 0x0103, 0x00ff, 0x08ff, 0x08ff, 0x08ff, 0x08ff, 0x08ff
.word 0x02ff, 0x02ff, 0x02ff, 0x00ff, 0x0103, 0x02ff, 0x02ff, 0x02ff .word 0x08ff, 0x00ff, 0x08ff, 0x00ff, 0x0103, 0x00ff, 0x08ff, 0x08ff
.word 0x02ff, 0x02ff, 0x02ff, 0x02ff # 0x03 Integer .word 0x08ff, 0x08ff, 0x08ff, 0x08ff # 0x03 Integer
.word 0x02ff, 0x02ff, 0x02ff, 0x02ff, 0x02ff, 0x04ff, 0x02ff, 0x02ff .word 0x02ff, 0x02ff, 0x02ff, 0x02ff, 0x02ff, 0x04ff, 0x02ff, 0x02ff
.word 0x02ff, 0x02ff, 0x02ff, 0x02ff, 0x02ff, 0x02ff, 0x02ff, 0x02ff .word 0x02ff, 0x02ff, 0x02ff, 0x02ff, 0x02ff, 0x02ff, 0x02ff, 0x02ff
@ -518,6 +518,9 @@ _tokenize_next:
li t0, 0x07 # An action for symbols containing multiple characters. li t0, 0x07 # An action for symbols containing multiple characters.
beq t1, t0, .Ltokenize_next_composite beq t1, t0, .Ltokenize_next_composite
li t0, 0x08 # Integer action.
beq t1, t0, .Ltokenize_next_integer
j .Ltokenize_next_reject j .Ltokenize_next_reject
.Ltokenize_next_reject: .Ltokenize_next_reject:
@ -588,6 +591,17 @@ _tokenize_next:
j .Ltokenize_next_end j .Ltokenize_next_end
.Ltokenize_next_integer:
lw a1, 12(sp)
sub a0, s1, a1
sw a0, 8(sp)
sw a0, 4(sp)
lw a0, 0(sp)
addi a1, sp, 4
li a2, 12
call _memcpy
j .Ltokenize_next_end
.Ltokenize_next_end: .Ltokenize_next_end:
mv a0, s1 # Return the advanced text pointer. mv a0, s1 # Return the advanced text pointer.

335
rakelib/cross.rake Normal file
View File

@ -0,0 +1,335 @@
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/. -}
# frozen_string_literal: true
require 'pathname'
require 'uri'
require 'net/http'
require 'rake/clean'
require 'open3'
require 'etc'
GCC_VERSION = "15.1.0"
BINUTILS_VERSION = '2.44'
GLIBC_VERSION = '2.41'
KERNEL_VERSION = '5.15.181'
CLOBBER.include 'build'
class BuildTarget
attr_accessor(:build, :gcc, :target, :tmp)
def gxx
@gcc.gsub 'c', '+'
end
def sysroot
tmp + 'sysroot'
end
def rootfs
tmp + 'rootfs'
end
def tools
tmp + 'tools'
end
end
def gcc_verbose(gcc_binary)
read, write = IO.pipe
sh({'LANG' => 'C'}, gcc_binary, '--verbose', err: write)
write.close
output = read.read
read.close
output
end
def find_build_target(gcc_version, task)
gcc_binary = 'gcc'
output = gcc_verbose gcc_binary
if output.start_with? 'Apple clang'
gcc_binary = "gcc-#{gcc_version.split('.').first}"
output = gcc_verbose gcc_binary
end
result = output
.lines
.each_with_object(BuildTarget.new) do |line, accumulator|
if line.start_with? 'Target: '
accumulator.build = line.split(' ').last.strip
elsif line.start_with? 'COLLECT_GCC'
accumulator.gcc = line.split('=').last.strip
end
end
result.tmp = Pathname.new('./build')
task.with_defaults target: 'riscv32-unknown-linux-gnu'
result.target = task[:target]
result
end
def download_and_unarchive(url, target)
case File.extname url.path
when '.bz2'
archive_type = '-j'
root_directory = File.basename url.path, '.tar.bz2'
when '.xz'
archive_type = '-J'
root_directory = File.basename url.path, '.tar.xz'
else
raise "Unsupported archive type #{url.path}."
end
Net::HTTP.start(url.host, url.port, use_ssl: url.scheme == 'https') do |http|
request = Net::HTTP::Get.new url.request_uri
http.request request do |response|
case response
when Net::HTTPRedirection
download_and_unarchive URI.parse(response['location'])
when Net::HTTPSuccess
Open3.popen2 'tar', '-C', target.to_path, archive_type, '-xv' do |stdin, stdout, wait_thread|
Thread.new do
stdout.each { |line| puts line }
end
response.read_body do |chunk|
stdin.write chunk
end
stdin.close
wait_thread.value
end
else
response.error!
end
end
end
target + root_directory
end
namespace :cross do
desc 'Build cross binutils'
task :binutils, [:target] do |_, args|
options = find_build_target GCC_VERSION, args
options.tools.mkpath
source_directory = download_and_unarchive(
URI.parse("https://ftp.gnu.org/gnu/binutils/binutils-#{BINUTILS_VERSION}.tar.xz"),
options.tools)
cwd = source_directory.dirname + 'build-binutils'
cwd.mkpath
options.rootfs.mkpath
env = {
'CC' => options.gcc,
'CXX' => options.gxx
}
configure_options = [
"--prefix=#{options.rootfs.realpath}",
"--target=#{options.target}",
'--disable-nls',
'--enable-gprofng=no',
'--disable-werror',
'--enable-default-hash-style=gnu',
'--disable-libquadmath'
]
configure = source_directory.relative_path_from(cwd) + 'configure'
sh env, configure.to_path, *configure_options, chdir: cwd.to_path
sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path
sh env, 'make', 'install', chdir: cwd.to_path
end
desc 'Build stage 1 GCC'
task :gcc1, [:target] do |_, args|
options = find_build_target GCC_VERSION, args
options.tools.mkpath
source_directory = download_and_unarchive(
URI.parse("https://gcc.gnu.org/pub/gcc/releases/gcc-#{GCC_VERSION}/gcc-#{GCC_VERSION}.tar.xz"),
options.tools)
cwd = source_directory.dirname + 'build-gcc'
cwd.mkpath
options.rootfs.mkpath
options.sysroot.mkpath
sh 'contrib/download_prerequisites', chdir: source_directory.to_path
configure_options = [
"--prefix=#{options.rootfs.realpath}",
"--with-sysroot=#{options.sysroot.realpath}",
'--enable-languages=c,c++',
'--disable-shared',
'--with-arch=rv32imafdc',
'--with-abi=ilp32d',
'--with-tune=rocket',
'--with-isa-spec=20191213',
'--disable-bootstrap',
'--disable-multilib',
'--disable-libmudflap',
'--disable-libssp',
'--disable-libquadmath',
'--disable-libsanitizer',
'--disable-threads',
'--disable-libatomic',
'--disable-libgomp',
'--disable-libvtv',
'--disable-libstdcxx',
'--disable-nls',
'--with-newlib',
'--without-headers',
"--target=#{options.target}",
"--build=#{options.build}",
"--host=#{options.build}"
]
flags = '-O2 -fPIC'
env = {
'CC' => options.gcc,
'CXX' => options.gxx,
'CFLAGS' => flags,
'CXXFLAGS' => flags,
'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}"
}
configure = source_directory.relative_path_from(cwd) + 'configure'
sh env, configure.to_path, *configure_options, chdir: cwd.to_path
sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path
sh env, 'make', 'install', chdir: cwd.to_path
end
desc 'Copy glibc headers'
task :headers, [:target] do |_, args|
options = find_build_target GCC_VERSION, args
options.tools.mkpath
source_directory = download_and_unarchive(
URI.parse("https://ftp.gnu.org/gnu/glibc/glibc-#{GLIBC_VERSION}.tar.xz"),
options.tools)
include_directory = options.tools + 'include'
include_directory.mkpath
cp (source_directory + 'elf/elf.h'), (include_directory + 'elf.h')
end
desc 'Build linux kernel'
task :kernel, [:target] do |_, args|
options = find_build_target GCC_VERSION, args
options.tools.mkpath
cwd = download_and_unarchive(
URI.parse("https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-#{KERNEL_VERSION}.tar.xz"),
options.tools)
env = {
'CROSS_COMPILE' => "#{options.target}-",
'ARCH' => 'riscv',
'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}",
'HOSTCFLAGS' => "-D_UUID_T -D__GETHOSTUUID_H -I#{options.tools.realpath + 'include'}"
}
sh env, 'make', 'rv32_defconfig', chdir: cwd.to_path
sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path
sh env, 'make', 'headers', chdir: cwd.to_path
user_directory = options.sysroot + 'usr'
user_directory.mkpath
cp_r (cwd + 'usr/include'), (user_directory + 'include')
end
desc 'Build glibc'
task :glibc, [:target] do |_, args|
options = find_build_target GCC_VERSION, args
source_directory = options.tools + "glibc-#{GLIBC_VERSION}"
configure_options = [
'--prefix=/usr',
"--host=#{options.target}",
"--target=#{options.target}",
"--build=#{options.build}",
"--enable-kernel=#{KERNEL_VERSION}",
"--with-headers=#{options.sysroot.realpath + 'usr/include'}",
'--disable-nscd',
'--disable-libquadmath',
'--disable-libitm',
'--disable-werror',
'libc_cv_forced_unwind=yes'
]
bin = options.rootfs.realpath + 'bin'
env = {
'PATH' => "#{bin}:#{ENV['PATH']}",
'MAKE' => 'make' # Otherwise it uses gnumake which can be different and too old.
}
cwd = source_directory.dirname + 'build-glibc'
cwd.mkpath
configure = source_directory.relative_path_from(cwd) +'./configure'
sh env, configure.to_path, *configure_options, chdir: cwd.to_path
sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path
sh env, 'make', "install_root=#{options.sysroot.realpath}", 'install', chdir: cwd.to_path
end
desc 'Build stage 2 GCC'
task :gcc2, [:target] do |_, args|
options = find_build_target GCC_VERSION, args
source_directory = options.tools + "gcc-#{GCC_VERSION}"
cwd = options.tools + 'build-gcc'
rm_rf cwd
cwd.mkpath
configure_options = [
"--prefix=#{options.rootfs.realpath}",
"--with-sysroot=#{options.sysroot.realpath}",
'--enable-languages=c,c++,lto',
'--enable-lto',
'--enable-shared',
'--with-arch=rv32imafdc',
'--with-abi=ilp32d',
'--with-tune=rocket',
'--with-isa-spec=20191213',
'--disable-bootstrap',
'--disable-multilib',
'--enable-checking=release',
'--disable-libssp',
'--disable-libquadmath',
'--enable-threads=posix',
'--with-default-libstdcxx-abi=new',
'--disable-nls',
"--target=#{options.target}",
"--build=#{options.build}",
"--host=#{options.build}"
]
flags = '-O2 -fPIC'
env = {
'CFLAGS' => flags,
'CXXFLAGS' => flags,
'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}"
}
configure = source_directory.relative_path_from(cwd) + 'configure'
sh env, configure.to_path, *configure_options, chdir: cwd.to_path
sh env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path
sh env, 'make', 'install', chdir: cwd.to_path
end
task :init, [:target] do |_, args|
options = find_build_target GCC_VERSION, args
env = {
'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}"
}
sh env, 'riscv32-unknown-linux-gnu-gcc',
'-ffreestanding', '-static',
'-o', (options.tools + 'init').to_path,
'tools/init.c'
end
end
desc 'Build cross toolchain'
task cross: [
'cross:binutils',
'cross:gcc1',
'cross:headers',
'cross:kernel',
'cross:glibc',
'cross:gcc2',
'cross:init'
] do
end

57
rakelib/stage.rake Normal file
View File

@ -0,0 +1,57 @@
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/. -}
# frozen_string_literal: true
CROSS_GCC = 'build/rootfs/bin/riscv32-unknown-linux-gnu-gcc'
SYSROOT = 'build/sysroot'
QEMU = 'qemu-riscv32'
def assemble_stage(output, compiler, source)
arguments = [QEMU, '-L', SYSROOT, *compiler]
puts Term::ANSIColor.green(arguments * ' ')
puts
Open3.popen2(*arguments) do |qemu_in, qemu_out|
qemu_in.write File.read(*source)
qemu_in.close
IO.copy_stream qemu_out, output
qemu_out.close
end
end
Dir.glob('boot/*.s').each do |assembly_source|
target_object = Pathname.new('build/boot') + Pathname.new(assembly_source).basename.sub_ext('.o')
file target_object.to_s => [assembly_source, 'build/boot'] do |t|
sh CROSS_GCC, '-c', '-o', t.name, assembly_source
end
end
desc 'Initial stage'
file 'build/boot/stage1' => ['build/boot/tokenizer.o', 'build/boot/stage1.o', 'build/boot/common-boot.o'] do |t|
sh CROSS_GCC, '-nostdlib', '-o', t.name, *t.prerequisites
end
file 'build/boot/stage2a.s' => ['build/boot/stage1', 'boot/stage2.elna'] do |t|
source, exe = t.prerequisites.partition { |prerequisite| prerequisite.end_with? '.elna' }
File.open t.name, 'w' do |output|
assemble_stage output, exe, source
end
end
['build/boot/stage2a', 'build/boot/stage2b'].each do |exe|
file exe => [exe.ext('.s'), 'build/boot/common-boot.o'] do |t|
sh CROSS_GCC, '-nostdlib', '-o', t.name, *t.prerequisites
end
end
file 'build/boot/stage2b.s' => ['build/boot/stage2a', 'boot/stage2.elna'] do |t|
source, exe = t.prerequisites.partition { |prerequisite| prerequisite.end_with? '.elna' }
File.open t.name, 'w' do |output|
assemble_stage output, exe, source
end
end

204
tools/init.c Normal file
View File

@ -0,0 +1,204 @@
#include <stdio.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/reboot.h>
#define FILENAME_BUFFER_SIZE 256
size_t read_command(int descriptor, char *command_buffer)
{
ssize_t bytes_read = 0;
size_t read_so_far = 0;
while ((bytes_read = read(descriptor, command_buffer + read_so_far, FILENAME_BUFFER_SIZE - read_so_far - 1)) > 0)
{
read_so_far += bytes_read;
if (read_so_far >= FILENAME_BUFFER_SIZE - 1)
{
break;
}
}
command_buffer[read_so_far] = 0;
return read_so_far;
}
enum status
{
status_success,
status_failure,
status_warning,
status_fatal
};
unsigned int make_path(char *destination, const char *directory, const char *filename, const char *extension)
{
unsigned int i = 0;
for (; i < FILENAME_BUFFER_SIZE; i++)
{
if (directory[i] == 0)
{
break;
}
destination[i] = directory[i];
}
for (int j = 0; i < FILENAME_BUFFER_SIZE; i++, j++)
{
if (filename[j] == 0)
{
break;
}
destination[i] = filename[j];
}
if (extension == NULL)
{
goto done;
}
for (int j = 0; i < FILENAME_BUFFER_SIZE; i++, j++)
{
if (extension[j] == 0)
{
break;
}
destination[i] = extension[j];
}
done:
destination[i] = 0;
return i;
}
enum status run_test(const char *file_entry_name)
{
printf("Running %s. ", file_entry_name);
char filename[FILENAME_BUFFER_SIZE];
char command_buffer[FILENAME_BUFFER_SIZE];
char file_buffer[256];
int pipe_ends[2];
if (pipe(pipe_ends) == -1)
{
perror("pipe");
return status_fatal;
}
make_path(filename, "./tests/", file_entry_name, NULL);
int child_pid = fork();
if (child_pid == -1)
{
return status_fatal;
}
else if (child_pid == 0)
{
close(STDIN_FILENO);
close(STDERR_FILENO);
close(pipe_ends[0]); // Close the read end.
if (dup2(pipe_ends[1], STDOUT_FILENO) == -1)
{
perror("dup2");
}
else
{
execl(filename, filename);
perror("execl");
}
close(STDOUT_FILENO);
close(pipe_ends[1]);
_exit(1);
}
else
{
close(pipe_ends[1]); // Close the write end.
read_command(pipe_ends[0], command_buffer);
close(pipe_ends[0]);
int wait_status = 0;
make_path(filename, "./expectations/", file_entry_name, ".txt");
FILE *expectation_descriptor = fopen(filename, "r");
if (expectation_descriptor == NULL)
{
return status_warning;
}
size_t read_from_file = fread(file_buffer, 1, sizeof(file_buffer) - 1, expectation_descriptor);
fclose(expectation_descriptor);
file_buffer[read_from_file] = 0;
for (unsigned int i = 0; ; ++i)
{
if (command_buffer[i] == 0 && file_buffer[i] == 0)
{
fwrite("\n", 1, 1, stdout);
return status_success;
}
else if (command_buffer[i] != file_buffer[i])
{
printf("Failed. Got:\n%s", command_buffer);
return status_failure;
}
}
}
}
struct summary
{
size_t total;
size_t failure;
size_t success;
};
void walk()
{
DIR *directory_stream = opendir("./tests");
struct dirent *file_entry;
struct summary test_summary = { .total = 0, .failure = 0, .success = 0 };
while ((file_entry = readdir(directory_stream)) != NULL)
{
if (file_entry->d_name[0] == '.')
{
continue;
}
++test_summary.total;
switch (run_test(file_entry->d_name))
{
case status_failure:
++test_summary.failure;
break;
case status_success:
++test_summary.success;
break;
case status_warning:
break;
case status_fatal:
goto end_walk;
}
}
printf("Successful: %lu, Failed: %lu, Total: %lu.\n",
test_summary.success, test_summary.failure, test_summary.total);
end_walk:
closedir(directory_stream);
}
int main()
{
int dev_console = open("/dev/console", O_WRONLY);
if (dev_console != -1)
{
dup2(dev_console, STDOUT_FILENO);
walk();
close(dev_console);
}
sync();
reboot(RB_POWER_OFF);
return 1;
}