1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
|
\section{Ассемблер: UTF-8 source, строки, .equ и arithmetic expressions}
Низкоуровневый mcpu-as обязан уметь описывать binary images data-like sections, labels и relocation-like address expressions без обязательного текстового препроцессора. Текстовая метапрограмма (\#define, \#include, conditional compilation, parameterized macros) остаётся отдельным будущим слоем. В assembler входят только механизмы, непосредственно связанные с machine/data representation и разрешением символов.
\subsection{Кодировка assembler source}
Исходный файл mcpu-as всегда UTF-8. Результат сборки не зависит от LANG, LC\_CTYPE или другой locale host-системы. Перед разбором assembler проверяет source как well-formed UTF-8 через LibMPUIO.
Это особенно важно для строковых директив: одинаковый .s обязан создавать одинаковые image bytes на любых build hosts при одинаковом LibMCPU target profile.
\subsection{Командная строка mcpu-as}
Ассемблер всегда создаёт один перемещаемый объект MELF ET\_REL, поэтому отдельный переключатель режима сборки не нужен. Выходной файл задаётся обычной UNIX-опцией \code{-o}, а единственный исходный файл является позиционным аргументом:
\begin{lstlisting}[language={}]
mcpu-as -o file.o file.s
mcpu-as file.s -o file.o
\end{lstlisting}
Обе формы полностью эквивалентны: порядок \code{-o} и входного файла свободный. Ровно один входной файл и ровно один выходной файл обязательны. Опции \code{-h} и \code{--help} печатают справку. Историческая форма \code{mcpu-as -c input.s output.o} удалена: \code{-c} больше не является опцией, поскольку другого режима работы у assembler нет.
\subsection{.utf8 - NUL-terminated UTF-8 byte string}
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.data
\end{lstlisting}
name\_utf8:
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.utf8 "Андрей"
\end{lstlisting}
.utf8 записывает UTF-8 payload буквально как bytes и добавляет один завершающий 0x00. Никакого target endian преобразования и никакой locale conversion нет.
"Андрей" -> D0 90 D0 BD D0 B4 D1 80 D0 B5 D0 B9 00
Поддерживаются escapes \textbackslash{}\textbackslash{}, \textbackslash{}", \textbackslash{}n, \textbackslash{}r, \textbackslash{}t, \textbackslash{}b, \textbackslash{}f, \textbackslash{}v, \textbackslash{}a, byte escape \textbackslash{}xHH, а также Unicode scalar escapes \textbackslash{}uXXXX и \textbackslash{}UXXXXXXXX. Результирующая последовательность снова проверяется как UTF-8.
Встроенный NUL внутри .utf8/.ucs2 намеренно не поддерживается: эти директивы определяют C-style NUL-terminated strings. Для произвольного binary layout с внутренними нулями используется .byte или .integer/.int.
\subsection{.ucs2 - UTF-8 source literal в strict UCS-2 target objects}
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.data
\end{lstlisting}
name\_ucs2:
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.ucs2 "Андрей"
\end{lstlisting}
Assembler сначала получает UTF-8 literal, затем использует strict LibMPUIO UTF-8 -> UCS-2 conversion. Каждый UCS-2 code unit записывается как отдельный 16-bit architectural data object с помощью target data byte order; в конце добавляется U+0000.
U+0410 U+043D U+0434 U+0440 U+0435 U+0439 U+0000
current little-endian target bytes:
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
10 04 3D 04 34 04 40 04 35 04 39 04 00 00
\end{lstlisting}
Strict UCS-2 допускает scalar values U+0000..U+FFFF, кроме surrogate range U+D800..U+DFFF. UTF-8 characters выше U+FFFF (например emoji) не раскладываются на UTF-16 surrogate pairs, а дают assembler error. Таким образом .ucs2 точно соответствует принятой в LibMCPU/LibMPUIO модели UCS-2, а не UTF-16.
\subsection{.byte, .integer и .int}
.byte создаёт raw bytes без implicit terminator, alignment или endian conversion:
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.byte 0x4c, 0x43, 0x50, 0x55
.byte 1, 2+3, BLOCK_SIZE/512
\end{lstlisting}
Каждый элемент должен находиться в диапазоне 0..255. .integer сохраняет прежний смысл typed integer object, а .int является полным синонимом. Обе директивы принимают список значений одной ширины:
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.integer 16, 0x0410, 0x043d, 0x0000
.int 8, 65, 66, 0
\end{lstlisting}
Каждое значение формирует отдельный object указанной width непосредственно в текущей data-like section position; hidden padding отсутствует.
\subsection{.equ - абсолютные assembler symbols}
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.equ table_ptr, 0x03000
.equ block_size, 4 * 512
.equ next_reg, table_ptr + 8
\end{lstlisting}
.equ section-independent и не создаёт bytes. Имена symbols case-sensitive. Все .equ definitions собираются до emission, поэтому разрешены forward references между .equ symbols.
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.equ является absolute constant symbol. В текущем low-level assembler выражение
\end{lstlisting}
справа может ссылаться на integer literals и другие .equ, но не на DATA/CODE labels. Это сохраняет различие между absolute assembler symbols и section-relative labels/relocations MELF.
Имя label не может совпадать с именем .equ.
\subsection{Integer expression grammar и precedence}
primary := integer | symbol | '(' expression ')'
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
unary := ['+' | '-'] unary | primary
\end{lstlisting}
product := unary \{ ('*' | '/' | '\%') unary \} expression := product \{ ('+' | '-') product \}
Операции имеют обычный arithmetic precedence: unary, затем multiply/divide/remainder, затем add/subtract. Division by zero, overflow internal expression domain и значение вне требуемой width дают assembler error.
Expressions используются для absolute numeric operands: register byte address, LDI, stack/pointer signed offsets, BMOVI count, .align, .byte, integer widths/values и других соответствующих immediate fields.
\subsection{Label expressions и fixups}
Address expressions дополнительно получают labels тех sections, которые допустимы для конкретного operand class. Поэтому assembler непосредственно поддерживает:
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
LEA r64@table_ptr, input_vector+8 ; selector follows label section
LOAD r64@next_reg, data_object+16 ; direct LOAD remains DATA-relative
JMP loop+0 ; PROGRAM CODE64
CALL function
.cptr handler+0
\end{lstlisting}
LEA принимает labels из DATA/CONTROL/INPUT/OUTPUT/TRACE и code sections и создаёт полный PTR64 selector+offset. Direct LOAD/STORE остаются в namespace DATA labels + .equ. CODE64 operands и .cptr используют PROGRAM labels + .equ. Forward labels работают через fixups. CPU по-прежнему не хранит instruction-boundary metadata: arithmetic CODE expression обязано использоваться программистом осмысленно.
\subsection{Symbolic register addresses}
REG24 byte address может быть integer expression из absolute .equ symbols:
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.equ table_ptr, 0x03000
.equ temp_ptr, table_ptr + 8
LEA r64@table_ptr, table
LDI r64@temp_ptr, 42
LDI r64@(table_ptr + 16), 7
\end{lstlisting}
После вычисления expression assembler выполняет обычную проверку register width, natural alignment и границ 512 KiB register file. DATA/CODE labels в register-address expression не участвуют: label address и register-file byte address являются разными namespaces.
\subsection{Граница mcpu-as и будущего препроцессора}
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
Механизм Уровень Причина
.init/.text/.fini/.data/.control/.input/.output/.trace, mcpu-as определяют layout
labels, .align executable/data
images
.integer/.int/.real/.complex/.byte/.utf8/.ucs2/.cptr mcpu-as создают data-like
objects/bytes
.equ, arithmetic expressions, symbol fixups mcpu-as необходимы для
адресов и будущих
relocations
#define/#undef/#include/#if/#ifdef отдельный текстовая
preprocessor metaprogramming не
является machine
encoding
parameterized macros / repetition отдельный source transformation
preprocessor до mcpu-as
\end{lstlisting}
Таким образом обычный symbolic assembler остаётся самодостаточным, а будущая схема .S -> cpu-cpp -> mcpu-as может добавляться независимо и не усложняет binary semantics cpu-as.
\subsection{Полный пример}
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.equ table_ptr, 0x03000
.equ next_reg, table_ptr + 8
.equ block_size, 4 * 512
.data
\end{lstlisting}
name16:
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.ucs2 "Андрей"
\end{lstlisting}
name8:
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.utf8 "Андрей"
\end{lstlisting}
header:
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.byte 0x4c, 0x43, 0x50, 0x55, block_size/512
\end{lstlisting}
letters:
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.int 16, 0x0410, 0x043d, 0x0434, 0
.align 16
\end{lstlisting}
answer:
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
.integer 64, 42
.text
\end{lstlisting}
start:
\begin{lstlisting}[language={},basicstyle=\ttfamily\scriptsize]
LEA r64@table_ptr, name16
LOAD r64@next_reg, answer
HALT
\end{lstlisting}
Здесь ни один identifier не требует textual macro substitution: table\_ptr/next\_reg - absolute assembler symbols, name16/answer - DATA labels, а arithmetic expression вычисляется самим assembler.
\subsection{Маркеры строк препроцессора и логические координаты diagnostics}
После появления \code{mcpu-cpp} assembler не использует собственную директиву \code{\#line} для управления source coordinates. Вместо неё \code{mcpu-as} распознаёт маркеры строк, оставляемые препроцессором в выходном потоке:
\begin{lstlisting}[language={}]
# digit-sequence
# digit-sequence "filename"
# digit-sequence "filename" flags...
\end{lstlisting}
\code{digit-sequence} содержит как минимум одну decimal digit и всегда разбирается в основании 10, даже при leading zeroes. Допустимый диапазон - 0..2147483647. Значение задаёт logical line number для physical source line, непосредственно следующей за marker. Filename optional; при его отсутствии сохраняется предыдущий logical filename. Всё после закрывающей кавычки filename считается полем flags препроцессора и полностью игнорируется assembler.
Marker не создаёт bytes, symbols, \code{__LINE__} или \code{__FILE__}, не участвует в expression evaluation и влияет только на source coordinates последующих diagnostics. Empty и comment-only ordinary lines увеличивают logical counter обычным образом.
Строки \code{\#pragma} и \code{\#lang} предназначены для других участников toolchain: \code{mcpu-as} полностью игнорирует их и не увеличивает logical line counter. Любая другая строка, начинающаяся с \code{\#}, также удаляется из assembler token stream, но учитывается как одна ordinary logical source line.
\begin{normative}
\textbf{Историческая форма \#line.} Запись \code{\#line N "file"} больше не является source-location directive \code{mcpu-as}. Она обрабатывается как обычная неизвестная строка \code{\#...}: игнорируется как assembler syntax и учитывается при продвижении logical counter. Координаты source задаются только preprocessing line marker формой \code{\# N "file" flags...}. Это diagnostics contract и не часть MELF ABI.
\end{normative}
|