lang-bootstrap/04b/in03

1084 lines
12 KiB
Text
Raw Normal View History

2021-11-19 09:52:27 -05:00
; initialize global_variables_end
C=:global_variables_end
D=:global_variables
8C=D
; initialize static_memory_end
C=:static_memory_end
D=x500000
2021-11-20 11:38:34 -05:00
8C=D
; initialize labels_end
C=:labels_end
D=:labels
8C=D
2021-11-19 09:52:27 -05:00
I=8S
A=d3
?I!A:usage_error
; open input file
J=S
; argv[1] is at *(rsp+16)
J+=d16
J=8J
I=d0
syscall x2
J=A
?J<0:input_file_error
; open output file
J=S
; argv[2] is at *(rsp+24)
J+=d24
J=8J
I=x241
D=x1ed
syscall x2
J=A
?J<0:output_file_error
:second_pass_starting_point
2021-11-19 23:27:08 -05:00
; write ELF header
J=d4
I=:ELF_header
D=x78
syscall x1
2021-11-19 09:52:27 -05:00
:read_line
; increment line number
D=:line_number
C=8D
C+=d1
8D=C
; use rbp to store line pointer
R=:line
:read_line_loop
; read 1 byte into rbp
J=d3
I=R
D=d1
syscall x0
D=A
?D=0:eof
; check if the character was a newline:
C=1R
D=xa
?C=D:read_line_loop_end
; check if the character was a tab:
D=x9
; if so, don't increment rbp
2021-11-19 23:27:08 -05:00
?C=D:read_line_loop
2021-11-19 09:52:27 -05:00
; check if the character was a semicolon:
D=';
; if so, it's a comment
?C=D:handle_comment
R+=d1
!:read_line_loop
:handle_comment
; read out rest of line from file
J=d3
I=R
D=d1
syscall x0
D=A
?D=0:eof
C=1R
D=xa
; if we didn't reach the end of the line, keep going
?C!D:handle_comment
!:read_line_loop_end
:read_line_loop_end
; remove whitespace (specifically, ' ' characters) at end of line
I=R
:remove_terminal_whitespace_loop
I-=d1
C=1I
D=x20
?C!D:remove_terminal_whitespace_loop_end
; replace ' ' with a newline
D=xa
1I=D
!:remove_terminal_whitespace_loop
:remove_terminal_whitespace_loop_end
; check if this is a blank line
C=:line
D=1C
C=xa
?C=D:read_line
2021-11-20 11:38:34 -05:00
C=':
?C=D:handle_label_definition
2021-11-19 09:52:27 -05:00
I=:line
J=:"global"
C=x20
call :string=
D=A
?D!0:handle_global
2021-11-19 23:27:08 -05:00
I=:line
J=:"local"
C=x20
call :string=
D=A
?D!0:handle_local
I=:line
J=:"argument"
C=x20
call :string=
D=A
?D!0:handle_local
I=:line
J=:"return"
C=x20
call :string=
D=A
?D!0:handle_return
C=xa
I=:line
J=:"function"
call :string=
D=A
?D!0:handle_function
2021-11-19 09:52:27 -05:00
!:read_line
:eof
C=:second_pass
D=1C
?D!0:exit_success
; set 2nd pass to 1
1C=d1
; seek both files back to start
J=d3
I=d0
D=d0
syscall x8
J=d4
I=d0
D=d0
syscall x8
!:second_pass_starting_point
:exit_success
2021-11-19 09:52:27 -05:00
J=d0
syscall x3c
2021-11-20 12:47:26 -05:00
align
:local_start
reserve d8
2021-11-19 23:27:08 -05:00
:handle_local
R=I
; emit sub rsp, 8
J=d4
I=:sub_rsp_8
D=d7
syscall x1
I=R
; skip ' '
I+=d1
call :read_type
R=A
I+=d1
2021-11-20 12:47:26 -05:00
; check if already defined
C=:local_start
8C=I
J=:local_variables
D=d5
2021-11-20 12:47:26 -05:00
call :ident_lookup
C=A
?C!0:local_redeclaration
C=:local_start
I=8C
2021-11-19 23:27:08 -05:00
J=:local_variables_end
J=8J
call :ident_copy
; store type
1J=R
J+=d1
; store address
D=:stack_end
D=4D
4J=D
J+=d4
2021-11-19 23:27:08 -05:00
; store null terminator
1J=0
; update :stack_end
D=:stack_end
C=8D
C+=d8
8D=C
; update :local_variables_end
I=:local_variables_end
8I=J
; read the next line
!:read_line
:sub_rsp_8
x48
x81
xec
x08
x00
x00
x00
2021-11-20 12:47:26 -05:00
align
:global_start
reserve d8
2021-11-19 09:52:27 -05:00
:handle_global
; ignore if this is the second pass
C=:second_pass
C=1C
?C!0:read_line
2021-11-19 23:27:08 -05:00
; skip ' '
I+=d1
2021-11-19 09:52:27 -05:00
call :read_type
; put type in R
R=A
; skip ' ' after type
I+=d1
2021-11-20 12:47:26 -05:00
; check if already defined
C=:global_start
8C=I
J=:global_variables
D=d5
2021-11-20 12:47:26 -05:00
call :ident_lookup
C=A
?C!0:global_redeclaration
C=:global_start
I=8C
2021-11-19 09:52:27 -05:00
J=:global_variables_end
J=8J
call :ident_copy
; store type
1J=R
J+=d1
; store address
D=:static_memory_end
D=4D
4J=D
J+=d4
2021-11-19 23:27:08 -05:00
; store null terminator
1J=0
2021-11-19 09:52:27 -05:00
; update :static_memory_end
D=:static_memory_end
C=8D
C+=d8
8D=C
; update :global_variables_end
I=:global_variables_end
8I=J
; go read the next line
!:read_line
2021-11-19 23:27:08 -05:00
:handle_function
; emit prologue
2021-11-19 23:27:08 -05:00
J=d4
I=:function_prologue
D=d14
2021-11-19 23:27:08 -05:00
syscall x1
; reset local variable table
D=:local_variables
1D=0
C=:local_variables_end
8C=D
; go read the next line
!:read_line
:function_prologue
; sub rsp, 8
x48
x81
xec
x08
x00
x00
x00
; mov [rsp], rbp
x48
x89
x2c
x24
; mov rbp, rsp
2021-11-19 23:27:08 -05:00
R=S
; total length: 7 + 4 + 3 = 14 bytes
:function_epilogue
; mov rsp, rbp
S=R
; mov rbp, [rsp]
x48
x8b
x2c
x24
; add rsp, 8
x48
x81
xc4
x08
x00
x00
x00
; ret
return
; total length = 15 bytes
2021-11-19 23:27:08 -05:00
2021-11-20 11:38:34 -05:00
:handle_label_definition
; ignore if this is the second pass
C=:second_pass
C=1C
?C!0:read_line
2021-11-20 11:38:34 -05:00
; make sure label only has identifier characters
I=:line
I+=d1
:label_checking_loop
C=1I
D=xa
?C=D:label_checking_loop_end
I+=d1
B=C
call :isident
D=A
?D!0:label_checking_loop
!:bad_label
:label_checking_loop_end
2021-11-20 12:47:26 -05:00
I=:line
I+=d1
J=:labels
D=d4
call :ident_lookup
C=A
?C!0:label_redefinition
2021-11-20 11:38:34 -05:00
J=:labels_end
J=8J
I=:line
I+=d1
call :ident_copy
R=J
J=d4
I=d0
D=d1
syscall x8
C=A
C+=x400000
J=R
4J=C
J+=d4
; update labels_end
C=:labels_end
8C=J
; read the next line
!:read_line
2021-11-19 23:27:08 -05:00
:handle_return
; @TODO: handle argument
J=d4
I=:function_epilogue
D=d15
2021-11-19 23:27:08 -05:00
syscall x1
; go read the next lines
!:read_line
:mov_rsp_rbp
S=R
:ret
return
2021-11-19 09:52:27 -05:00
; copy the newline-terminated identifier from rsi to rdi
:ident_copy
C=1I
B=C
call :isident
D=A
?D=0:bad_identifier
:ident_loop
C=1I
2021-11-20 11:38:34 -05:00
1J=C
I+=d1
J+=d1
2021-11-19 09:52:27 -05:00
D=xa
?C=D:ident_loop_end
B=C
call :isident
D=A
?D=0:bad_identifier
!:ident_loop
:ident_loop_end
return
2021-11-20 12:47:26 -05:00
align
:ident_lookup_i
reserve d8
:ident_lookup_sep
reserve d8
; look up identifier rsi in list rdi with separation rdx between entries
; returns address of whatever's right after the identifier in the list, or 0 if not found
:ident_lookup
C=:second_pass
C=1C
; use default of 0 on first pass
?C!0:return_0
2021-11-20 12:47:26 -05:00
C=:ident_lookup_sep
8C=D
C=:ident_lookup_i
8C=I
:ident_lookup_loop
I=:ident_lookup_i
I=8I
call :ident=
C=A
; move past terminator
J+=d1
?C!0:return_J
C=:ident_lookup_sep
C=8C
J+=C
C=1J
?C=0:return_0
!:ident_lookup_loop
2021-11-19 09:52:27 -05:00
; can the character in rbx appear in an identifier?
:isident
A='0
?B<A:return_0
; note: 58 = '9' + 1
A=d58
?B<A:return_1
A='A
?B<A:return_0
; note: 91 = 'z' + 1
A=d91
?B<A:return_1
A='z
?B>A:return_0
; 96 = 'a' - 1
A=d96
?B>A:return_1
A='_
?B=A:return_1
!:return_0
; set rax to the term in rsi
:set_rax_to_term
R=I
C=1I
D=''
?C=D:term_char
C=1I
D=d58
?C<D:term_number
; variable
J=:local_variables
D=d5
call :ident_lookup
C=A
?C=0:rax2term_try_global
; it's a local variable
; skip over its type
C+=d1
; read the offset from rbp
D=4C
; put negated offset in rcx
C=d0
C-=D
; store negated offset in :rax2term_addr
D=:rax2term_addr
4D=C
J=d4
I=:load_rbp_offset_prefix
D=d3
syscall x1
J=d4
I=:rax2term_addr
D=d4
syscall x1
:rax2term_try_global
J=:global_variables
D=d5
call :ident_lookup
C=A
?C=0:bad_term
; it's a global variable
; skip over its type
C+=d1
C=4C
D=:rax2term_addr
4D=C
; put address in rbx
J=d4
I=:mov_ebx_imm32_prefix
D=d1
syscall x1
J=d4
I=:rax2term_addr
D=d4
syscall x1
; now load [rbx] into rax
J=d4
I=:mov_rax_[rbx]
D=d3
syscall x1
return
:term_char
; @TODO
xcc
:term_number
; @TODO
xcc
align
:rax2term_addr
reserve d4
:mov_ebx_imm32_prefix
xbb
:mov_rax_[rbx]
x48
x8b
x03
; prefix for mov rax, [rbp+IMM32]
:load_rbp_offset_prefix
x48
x8b
x85
2021-11-19 09:52:27 -05:00
; read the space-terminated type from rsi, advance rsi, and set rax to the corresponding type number:
; 0 for non-pointer types
; 1 for pointer to char
; 2 for pointer to short
; 4 for pointer to int
; 8 for pointer to long
:read_type
C=1I
D='*
?C=D:read_pointer_type
; it's not a pointer
call :read_simple_type
A=d0
return
:read_pointer_type
; it's a pointer!
I+=d1
!:read_simple_type
; returns 1 for char, 2 for short, 4 for int, 8 for long
:read_simple_type
R=I
C=x20
I=R
J=:"char"
call :string=
D=A
?D!0:return_1
I=R
J=:"short"
call :string=
D=A
?D!0:return_2
I=R
J=:"int"
call :string=
D=A
?D!0:return_4
I=R
J=:"long"
call :string=
D=A
?D!0:return_8
!:bad_type
:usage_error
B=:usage_error_message
call :general_error
:usage_error_message
str Please provide an input and an output file.
xa
x0
:input_file_error
B=:input_file_error_message
!:general_error
:input_file_error_message
str Couldn't open input file.
xa
x0
:output_file_error
B=:output_file_error_message
!:general_error
:output_file_error_message
str Couldn't open output file.
xa
x0
:bad_identifier
B=:bad_identifier_error_message
!:program_error
:bad_identifier_error_message
str Bad identifier.
xa
x0
2021-11-20 11:38:34 -05:00
:bad_label
B=:bad_label_error_message
!:program_error
:bad_label_error_message
str Bad label.
xa
x0
2021-11-19 09:52:27 -05:00
:bad_type
B=:bad_type_error_message
!:program_error
:bad_type_error_message
str Bad type.
xa
x0
:bad_term
B=:bad_term_error_message
!:program_error
:bad_term_error_message
str Bad term.
xa
x0
2021-11-20 12:47:26 -05:00
:label_redefinition
B=:label_redefinition_error_message
!:program_error
:label_redefinition_error_message
str Label redefinition.
xa
x0
:global_redeclaration
B=:global_redeclaration_error_message
!:program_error
:global_redeclaration_error_message
str Global variable declared twice.
xa
x0
:local_redeclaration
B=:local_redeclaration_error_message
!:program_error
:local_redeclaration_error_message
str Local variable declared twice.
xa
x0
2021-11-19 09:52:27 -05:00
:general_error
call :eputs
J=d1
syscall x3c
:program_error
R=B
B=:"Line"
call :eputs
D=:line_number
D=8D
B=D
call :eputn
B=:line_number_separator
call :eputs
B=R
call :eputs
J=d1
syscall x3c
:"Line"
str Line
x20
x0
:line_number_separator
str :
x20
x0
:strlen
I=B
D=B
:strlen_loop
C=1I
?C=0:strlen_ret
I+=d1
!:strlen_loop
:strlen_ret
I-=D
A=I
return
; check if strings in rdi and rsi are equal, up to terminator in rcx
:string=
D=1I
A=1J
?D!A:return_0
?D=C:return_1
I+=d1
J+=d1
!:string=
; check if strings in rdi and rsi are equal, up to the first non-identifier character
:ident=
D=1I
B=D
call :isident
; I ended
?A=0:ident=_I_end
D=1J
B=D
call :isident
; J ended, but I didn't
?A=0:return_0
; we haven't reached the end of either
D=1I
A=1J
?D!A:return_0
I+=d1
J+=d1
!:ident=
:ident=_I_end
D=1J
B=D
call :isident
; check if J also ended
?A=0:return_1
; J didn't end
!:return_0
:return_0
A=d0
return
:return_1
A=d1
return
:return_2
A=d2
return
:return_3
A=d3
return
:return_4
A=d4
return
:return_5
A=d5
return
:return_6
A=d6
return
:return_7
A=d7
return
:return_8
A=d8
return
2021-11-20 12:47:26 -05:00
:return_J
A=J
return
2021-11-19 09:52:27 -05:00
; write the character in rbx to the file in rdi.
:fputc
C=B
I=S
I-=d1
1I=C
D=d1
syscall x1
return
; write the string in rbx to stderr
:eputs
J=B
call :strlen
D=A
I=J
J=d2
syscall x1
return
; write rbx in decimal to stderr
:eputn
I=B
J=S
J-=d1
:eputn_loop
D=d0
; divide by 10
B=d10
A=I
div
; quotient is new number
I=A
; add remainder to string
D+='0
1J=D
J-=d1
?I!0:eputn_loop
D=S
D-=J
I=J
J=d2
syscall x1
return
; copy rdx bytes from rsi to rdi.
; this copies from the left: if you're doing an overlapped copy, rsi should be greater than rdi
:memcpy
?D=0:return_0
A=1I
1J=A
I+=d1
J+=d1
D-=d1
!:memcpy
2021-11-20 11:38:34 -05:00
; copy from rdi to rsi, until byte cl is reached
:memccpy
D=1I
1J=D
I+=d1
J+=d1
?D!C:memccpy
return
2021-11-19 23:27:08 -05:00
:"char"
str char
x20
:"short"
str short
x20
:"int"
str int
x20
:"long"
str long
x20
:"global"
str global
x20
:"argument"
str argument
x20
:"local"
str local
x20
:"return"
str return
x20
:"function"
str function
xa
2021-11-19 09:52:27 -05:00
; put a 0 byte before the line (this is important for removing whitespace at the end of the line,
; specifically, we don't want this to be a space character)
x0
:line
reserve d1000
align
:global_variables_end
reserve d8
:static_memory_end
reserve d8
2021-11-19 23:27:08 -05:00
:local_variables_end
reserve d8
:stack_end
reserve d8
2021-11-20 11:38:34 -05:00
:labels_end
reserve d8
2021-11-19 09:52:27 -05:00
:line_number
reserve d8
:global_variables
reserve d50000
2021-11-19 23:27:08 -05:00
:local_variables
reserve d20000
2021-11-20 11:38:34 -05:00
:labels
2021-11-19 23:27:08 -05:00
reserve d200000
:second_pass
reserve d1
2021-11-19 23:27:08 -05:00
:ELF_header
x7f
x45
x4c
x46
x02
x01
x01
reserve d9
x02
x00
x3e
x00
x01
x00
x00
x00
x78
x00
x40
x00
x00
x00
x00
x00
x40
x00
x00
x00
x00
x00
x00
x00
reserve d12
x40
x00
x38
x00
x01
x00
x00
x00
x00
x00
x00
x00
x01
x00
2021-11-19 09:52:27 -05:00
x00
2021-11-19 23:27:08 -05:00
x00
x07
x00
x00
x00
x78
x00
x00
x00
x00
x00
x00
x00
x78
x00
x40
x00
x00
x00
x00
x00
reserve d8
x00
x00
x20
x00
x00
x00
x00
x00
x00
x00
x20
x00
x00
x00
x00
x00
x00
x10
x00
x00
x00
x00
x00
x00
; NOTE: we shouldn't end the file with a reserve; we don't handle that properly