1use crate::AsmError;
2use crate::aarch64::operands::*;
3use crate::aarch64::{Gp, Reg, instdb::*};
4use crate::core::arch_traits::Arch;
5use crate::core::buffer::CodeBuffer;
6use crate::core::buffer::{Constant, LabelUse, Reloc, RelocDistance, RelocTarget};
7use crate::core::globals::CondCode;
8use crate::core::operand::*;
9use crate::core::target::Environment;
10
11pub struct Assembler<'a> {
12 pub(crate) buffer: &'a mut CodeBuffer,
13 pub(crate) last_error: Option<AsmError>,
15}
16
17fn validate_raw_operand(buffer: &CodeBuffer, op: &Operand) -> bool {
18 let Some(op_type) = op.signature.try_op_type() else {
19 return false;
20 };
21
22 match op_type {
23 OperandType::None => op.signature.bits() == 0,
24 OperandType::Reg => {
25 let Some(reg_type) = op.signature.try_reg_type() else {
26 return false;
27 };
28 let Some(_) = op.signature.try_reg_group() else {
29 return false;
30 };
31 let expected = Reg::signature_of(reg_type);
32 let mask = OperandSignature::OP_TYPE_MASK
33 | OperandSignature::REG_TYPE_MASK
34 | OperandSignature::REG_GROUP_MASK
35 | OperandSignature::SIZE_MASK;
36 expected.bits() != 0
37 && op.signature.subset(mask) == expected.subset(mask)
38 && match reg_type {
39 RegType::Gp32 | RegType::Gp64 => op.id() <= 31 || op.id() == Gp::ID_ZR,
40 RegType::Vec8
41 | RegType::Vec16
42 | RegType::Vec32
43 | RegType::Vec64
44 | RegType::Vec128 => op.id() <= 31,
45 _ => false,
46 }
47 }
48 OperandType::Mem => {
49 let Some(base_type) = op.signature.try_mem_base_type() else {
50 return false;
51 };
52 let Some(index_type) = op.signature.try_mem_index_type() else {
53 return false;
54 };
55 let mem = op.as_::<BaseMem>();
56 let offset_mode = op
57 .signature
58 .get_field::<{ Mem::SIGNATURE_MEM_OFFSET_MODE_MASK }>();
59 let shift_op = op
60 .signature
61 .get_field::<{ Mem::SIGNATURE_MEM_SHIFT_OP_MASK }>();
62 let valid_base = match base_type {
63 RegType::None | RegType::LabelTag | RegType::SymTag => true,
64 RegType::Gp32 | RegType::Gp64 => mem.base_id() <= 31,
65 _ => false,
66 };
67 let valid_index = match index_type {
68 RegType::None => mem.index_id() == 0,
69 RegType::Gp32 | RegType::Gp64 => mem.index_id() <= 31,
70 _ => false,
71 };
72 valid_base
73 && valid_index
74 && offset_mode <= OffsetMode::PostIndex as u32
75 && shift_op <= 13
76 && (!mem.has_base_sym()
77 || buffer.symbol_name(Sym::from_id(mem.base_id())).is_some())
78 }
79 OperandType::Sym => buffer.symbol_name(Sym::from_id(op.id())).is_some(),
80 OperandType::Imm | OperandType::Label => true,
81 OperandType::RegList => false,
82 }
83}
84
85impl crate::core::builder::InstSink for Assembler<'_> {
86 fn arch(&self) -> Arch {
87 self.environment().arch()
88 }
89
90 fn emit_inst(&mut self, inst: &crate::core::inst::Inst) -> Result<(), AsmError> {
91 let ops = inst.operands();
92 let mut refs: smallvec::SmallVec<[&Operand; 6]> = smallvec::SmallVec::new();
93 refs.extend(ops.iter());
94 self.try_emit_n(inst.id(), &refs)
95 }
96
97 fn bind_label(&mut self, label: Label) -> Result<(), AsmError> {
98 self.try_bind_label(label)
99 }
100}
101
102pub trait LoadConstantEmitter<DST, SRC> {
103 fn load_constant(&mut self, dst: DST, src: SRC);
104}
105
106impl LoadConstantEmitter<Gp, Constant> for Assembler<'_> {
107 fn load_constant(&mut self, dst: Gp, src: Constant) {
108 let label = self.buffer.get_label_for_constant(src);
109 self.load_constant(dst, label);
110 }
111}
112
113impl LoadConstantEmitter<Gp, Label> for Assembler<'_> {
114 fn load_constant(&mut self, dst: Gp, src: Label) {
115 self.adrp(dst, src);
116 self.buffer
117 .use_label_at_offset(self.buffer.cur_offset(), src, LabelUse::A64AddAbsLo12);
118 self.add(dst, dst, imm(0));
119 }
120}
121
122impl LoadConstantEmitter<Gp, Sym> for Assembler<'_> {
123 fn load_constant(&mut self, dst: Gp, src: Sym) {
124 let Some(distance) = self.buffer.symbol_distance(src) else {
125 self.buffer.record_error(AsmError::InvalidArgument);
126 return;
127 };
128
129 if self.buffer.env().pic() {
130 self.buffer
132 .add_reloc(Reloc::Aarch64AdrGotPage21, RelocTarget::Sym(src), 0);
133 self.adrp(dst, imm(0));
134 self.buffer
135 .add_reloc(Reloc::Aarch64Ld64GotLo12Nc, RelocTarget::Sym(src), 0);
136 self.ldr(dst, ptr(dst, 0));
137 return;
138 }
139
140 match distance {
141 RelocDistance::Near => {
142 self.buffer
143 .add_reloc(Reloc::Aarch64AdrPrelPgHi21, RelocTarget::Sym(src), 0);
144 self.adrp(dst, imm(0));
145 self.buffer
146 .add_reloc(Reloc::Aarch64AddAbsLo12Nc, RelocTarget::Sym(src), 0);
147 self.add(dst, dst, imm(0));
148 }
149
150 RelocDistance::Far => {
151 let constant_start = self.buffer.get_label();
159 let constant_end = self.buffer.get_label();
160 self.ldr(dst, label_ptr(constant_start, 0));
161 self.b(constant_end);
162 self.buffer.bind_label(constant_start);
163 self.buffer.add_reloc(Reloc::Abs8, RelocTarget::Sym(src), 0);
164 self.buffer.write_u64(0);
165 self.buffer.bind_label(constant_end);
166 }
167 }
168 }
169}
170
171impl<'a> Assembler<'a> {
172 pub fn new(buffer: &'a mut CodeBuffer) -> Self {
173 if buffer.env().arch() != Arch::AArch64 {
174 return Self::poisoned(buffer, AsmError::InvalidArch);
175 }
176 Self::unchecked(buffer)
177 }
178
179 pub fn try_new(buffer: &'a mut CodeBuffer) -> Result<Self, AsmError> {
180 if buffer.env().arch() != Arch::AArch64 {
181 return Err(AsmError::InvalidArch);
182 }
183 Ok(Self::unchecked(buffer))
184 }
185
186 fn unchecked(buffer: &'a mut CodeBuffer) -> Self {
187 Self {
188 buffer,
189 last_error: None,
190 }
191 }
192
193 fn poisoned(buffer: &'a mut CodeBuffer, error: AsmError) -> Self {
194 buffer.record_error(error);
195 Self::unchecked(buffer)
196 }
197
198 pub fn environment(&self) -> &Environment {
200 self.buffer.env()
201 }
202
203 pub fn is_32bit(&self) -> bool {
205 self.buffer.env().is_32bit()
206 }
207
208 pub fn is_64bit(&self) -> bool {
210 self.buffer.env().is_64bit()
211 }
212
213 pub fn get_label(&mut self) -> Label {
214 self.buffer.get_label()
215 }
216
217 pub fn bind_label(&mut self, label: Label) {
218 if let Err(error) = self.try_bind_label(label) {
219 self.buffer.record_error(error);
220 }
221 }
222
223 pub fn try_bind_label(&mut self, label: Label) -> Result<(), AsmError> {
224 self.buffer.try_bind_label(label)
225 }
226
227 pub fn load_constant<DST, SRC>(&mut self, dst: DST, src: SRC)
244 where
245 Self: LoadConstantEmitter<DST, SRC>,
246 {
247 if self.buffer.error().is_some() {
248 return;
249 }
250 let checkpoint = self.buffer.checkpoint();
251 <Self as LoadConstantEmitter<DST, SRC>>::load_constant(self, dst, src);
252 if self.buffer.error().is_some() {
253 self.buffer.rollback(checkpoint);
254 }
255 }
256
257 #[cfg(test)]
258 fn last_error(&self) -> Option<AsmError> {
259 self.buffer.error().cloned()
260 }
261
262 pub fn emit_n(&mut self, id: impl Into<u32>, ops: &[&Operand]) {
263 if let Err(error) = self.try_emit_n(id, ops) {
264 self.buffer.record_error(error);
265 }
266 }
267
268 pub fn try_emit_n(&mut self, id: impl Into<u32>, ops: &[&Operand]) -> Result<(), AsmError> {
269 if let Some(error) = self.buffer.error().cloned() {
270 return Err(error);
271 }
272 if ops.len() > 6 || ops.iter().any(|op| !validate_raw_operand(self.buffer, op)) {
273 return Err(AsmError::InvalidOperand);
274 }
275 let id = id.into();
276 let checkpoint = self.buffer.checkpoint();
277 self.last_error = None;
278 self._emit(id, ops);
279 if let Some(error) = self.last_error.take() {
280 self.buffer.rollback(checkpoint);
281 return Err(error);
282 }
283 if let Some(error) = self.buffer.error().cloned() {
284 self.buffer.rollback(checkpoint);
285 return Err(error);
286 }
287 Ok(())
288 }
289
290 pub fn data(&self) -> &[u8] {
291 self.buffer.data()
292 }
293
294 pub fn relocs(&self) -> &[crate::core::buffer::AsmReloc] {
295 self.buffer.relocs()
296 }
297
298 pub fn error(&self) -> Option<&AsmError> {
299 self.buffer.error()
300 }
301}
302
303impl InstId {
304 pub const ARM_COND: u32 = 0x78000000;
305 pub const REAL_ID: u32 = 65535;
306 pub const fn with_cc(self, cond: CondCode) -> u32 {
307 let x = self as u32;
308 x | (cond as u32) << Self::ARM_COND.trailing_zeros()
309 }
310
311 pub const fn extract_cc(inst: u32) -> CondCode {
312 unsafe {
313 core::mem::transmute(((inst & Self::ARM_COND) >> Self::ARM_COND.trailing_zeros()) as u8)
314 }
315 }
316
317 pub const fn extract_real_id(inst: u32) -> u32 {
318 inst & Self::REAL_ID
319 }
320}
321
322pub const BLE: u32 = InstId::B.with_cc(CondCode::GE);
323
324impl From<InstId> for u32 {
325 fn from(inst: InstId) -> Self {
326 inst as u32
327 }
328}
329
330pub(super) use crate::aarch64::encoder::OffsetType;
332pub use crate::aarch64::encoder::{
333 LogicalImm, count_zero_half_words_64, encode_fp64_to_imm8, encode_logical_imm, is_fp16_imm8,
334 is_fp32_imm8, is_fp64_imm8,
335};
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340 use crate::aarch64::operands::regs;
341 use crate::core::buffer::RelocDistance;
342
343 #[test]
344 fn pic_symbol_load_uses_only_the_got_sequence() {
345 for distance in [RelocDistance::Near, RelocDistance::Far] {
346 let mut environment = Environment::new(Arch::AArch64);
347 environment.set_pic(true);
348 let mut buffer = CodeBuffer::new(environment);
349 let symbol = buffer.extern_sym("external", distance);
350
351 {
352 let mut asm = Assembler::new(&mut buffer);
353 asm.load_constant(regs::x(0), symbol);
354 assert_eq!(
355 asm.buffer.data(),
356 &[0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x40, 0xF9]
357 );
358 assert_eq!(asm.buffer.relocs().len(), 2);
359 assert_eq!(asm.buffer.relocs()[0].offset, 0);
360 assert_eq!(asm.buffer.relocs()[0].kind, Reloc::Aarch64AdrGotPage21);
361 assert_eq!(asm.buffer.relocs()[0].target, RelocTarget::Sym(symbol));
362 assert_eq!(asm.buffer.relocs()[0].addend, 0);
363 assert_eq!(asm.buffer.relocs()[1].offset, 4);
364 assert_eq!(asm.buffer.relocs()[1].kind, Reloc::Aarch64Ld64GotLo12Nc);
365 assert_eq!(asm.buffer.relocs()[1].target, RelocTarget::Sym(symbol));
366 assert_eq!(asm.buffer.relocs()[1].addend, 0);
367 }
368 }
369 }
370
371 #[test]
372 fn invalid_raw_instruction_ids_are_rejected() {
373 let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
374 let mut asm = Assembler::new(&mut buffer);
375
376 asm.emit_n(0u32, &[]);
377 assert_eq!(asm.last_error(), Some(AsmError::InvalidInstruction));
378 assert!(asm.buffer.data().is_empty());
379
380 asm.buffer.clear();
381 asm.emit_n(u32::MAX, &[]);
382 assert_eq!(asm.last_error(), Some(AsmError::InvalidInstruction));
383 assert!(asm.buffer.data().is_empty());
384
385 asm.buffer.clear();
386 asm.emit_n(InstId::Add as u32 | (1 << 16), &[]);
387 assert_eq!(asm.last_error(), Some(AsmError::InvalidInstruction));
388 assert!(asm.buffer.data().is_empty());
389 }
390
391 #[test]
392 fn baseline_rejects_optional_features_before_writing() {
393 let mut buffer = CodeBuffer::new(Environment::baseline(Arch::AArch64));
394 let mut asm = Assembler::new(&mut buffer);
395
396 let error = asm
397 .try_emit_n(
398 InstId::Crc32b,
399 &[
400 regs::w(0).as_operand(),
401 regs::w(1).as_operand(),
402 regs::w(2).as_operand(),
403 ],
404 )
405 .unwrap_err();
406
407 assert!(
408 matches!(error, AsmError::MissingCpuFeature { feature } if feature.contains("crc32b") && feature.contains("CRC32"))
409 );
410 assert!(asm.buffer.data().is_empty());
411 }
412
413 #[test]
414 fn optional_features_are_enabled_by_default() {
415 let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
416 let mut asm = Assembler::new(&mut buffer);
417
418 asm.try_emit_n(
419 InstId::Crc32b,
420 &[
421 regs::w(0).as_operand(),
422 regs::w(1).as_operand(),
423 regs::w(2).as_operand(),
424 ],
425 )
426 .unwrap();
427
428 assert_eq!(asm.buffer.data().len(), 4);
429 }
430
431 #[test]
432 fn mixed_instruction_checks_the_selected_form() {
433 let mut environment = Environment::baseline(Arch::AArch64);
434 environment.set_aarch64_feature(crate::aarch64::CpuFeature::Asimd, true);
435 let mut buffer = CodeBuffer::new(environment);
436 let mut asm = Assembler::new(&mut buffer);
437
438 asm.try_emit_n(
439 InstId::Fadd_v,
440 &[
441 regs::s(0).as_operand(),
442 regs::s(1).as_operand(),
443 regs::s(2).as_operand(),
444 ],
445 )
446 .unwrap();
447 let accepted_len = asm.buffer.data().len();
448
449 let error = asm
450 .try_emit_n(
451 InstId::Fadd_v,
452 &[
453 regs::h(0).as_operand(),
454 regs::h(1).as_operand(),
455 regs::h(2).as_operand(),
456 ],
457 )
458 .unwrap_err();
459
460 assert!(
461 matches!(error, AsmError::MissingCpuFeature { feature } if feature.contains("fadd Hd") && feature.contains("FP16"))
462 );
463 assert_eq!(asm.buffer.data().len(), accepted_len);
464 }
465
466 #[test]
467 fn raw_emission_rejects_malformed_or_extra_operands_without_mutation() {
468 let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
469 let mut asm = Assembler::new(&mut buffer);
470 let malformed = Operand {
471 signature: OperandSignature::from(7),
472 base_id: 0,
473 data: [0; 2],
474 };
475 let invalid_register = Operand {
476 signature: OperandSignature::from(
477 OperandType::Reg as u32 | (31 << OperandSignature::REG_TYPE_SHIFT),
478 ),
479 base_id: 0,
480 data: [0; 2],
481 };
482 let none = Operand::new();
483 let mut invalid_memory = ptr(regs::x(0), 0);
484 invalid_memory.set_base_id(64);
485 let mut invalid_mode = ptr(regs::x(0), 0);
486 invalid_mode
487 .signature
488 .set_field::<{ Mem::SIGNATURE_MEM_OFFSET_MODE_MASK }>(3);
489
490 assert_eq!(
491 asm.try_emit_n(InstId::Add as u32, &[&malformed]),
492 Err(AsmError::InvalidOperand)
493 );
494 assert_eq!(
495 asm.try_emit_n(InstId::Add as u32, &[&invalid_register]),
496 Err(AsmError::InvalidOperand)
497 );
498 assert_eq!(
499 asm.try_emit_n(
500 InstId::Ldr as u32,
501 &[regs::x(1).as_operand(), invalid_memory.as_operand()],
502 ),
503 Err(AsmError::InvalidOperand)
504 );
505 assert_eq!(
506 asm.try_emit_n(
507 InstId::Ldr as u32,
508 &[regs::x(1).as_operand(), invalid_mode.as_operand()],
509 ),
510 Err(AsmError::InvalidOperand)
511 );
512 assert_eq!(
513 asm.try_emit_n(
514 InstId::Add as u32,
515 &[&none, &none, &none, &none, &none, &none, &none],
516 ),
517 Err(AsmError::InvalidOperand)
518 );
519 assert!(asm.buffer.error().is_none());
520 assert!(asm.buffer.data().is_empty());
521 }
522
523 #[test]
524 fn raw_label_registration_error_rolls_back_emission() {
525 let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
526 let mut asm = Assembler::new(&mut buffer);
527 let invalid_label = Label::from_id(0);
528
529 assert_eq!(
530 asm.try_emit_n(InstId::B as u32, &[invalid_label.as_operand()]),
531 Err(AsmError::InvalidArgument)
532 );
533 assert_eq!(asm.buffer.error(), Some(&AsmError::InvalidArgument));
534 assert!(asm.buffer.data().is_empty());
535 }
536
537 #[test]
538 fn failed_constant_load_rolls_back_the_whole_sequence() {
539 let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
540 let mut asm = Assembler::new(&mut buffer);
541
542 asm.load_constant(regs::x(0), Label::from_id(0));
543
544 assert_eq!(asm.buffer.error(), Some(&AsmError::InvalidArgument));
545 assert!(asm.buffer.data().is_empty());
546 }
547
548 #[test]
549 fn invalid_symbol_load_sets_error_without_mutation() {
550 let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
551 let mut asm = Assembler::new(&mut buffer);
552
553 asm.load_constant(regs::x(0), Sym::from_id(u32::MAX));
554
555 assert_eq!(asm.buffer.error(), Some(&AsmError::InvalidArgument));
556 assert!(asm.buffer.data().is_empty());
557 }
558}