Skip to content

Commit 577e657

Browse files
committed
arm32: fix a bunch of bugs
Signed-off-by: Obei Sideg <obei.sideg@gmail.com>
1 parent a1695bd commit 577e657

16 files changed

Lines changed: 593 additions & 145 deletions

File tree

cranelift/codegen/meta/src/isa/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ impl Isa {
3434
pub fn from_arch(arch: &str) -> Option<Self> {
3535
match arch {
3636
"aarch64" => Some(Isa::Arm64),
37-
x if x.starts_with("arm") || x.starts_with("thumb") => Some(Isa::Arm32),
37+
// 32-bit ARM / Thumb, but not the 64-bit `arm64*` spellings
38+
// (e.g. `arm64ec`), which are AArch64.
39+
x if (x.starts_with("arm") || x.starts_with("thumb")) && !x.starts_with("arm64") => {
40+
Some(Isa::Arm32)
41+
}
3842
"s390x" => Some(Isa::S390x),
3943
x if ["x86_64", "i386", "i586", "i686"].contains(&x) => Some(Isa::X86),
4044
"riscv64" | "riscv64gc" | "riscv64imac" => Some(Isa::Riscv64),

cranelift/codegen/src/isa/arm32/abi.rs

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -231,18 +231,36 @@ impl ABIMachineSpec for Arm32MachineDeps {
231231
}
232232

233233
fn gen_load_base_offset(into_reg: Writable<Reg>, base: Reg, offset: i32, _ty: Type) -> Inst {
234-
Inst::Load {
235-
rt: into_reg,
236-
mem: AMode::RegOffset { rn: base, offset },
237-
kind: LoadKind::Word,
234+
let mem = AMode::RegOffset { rn: base, offset };
235+
if into_reg.to_reg().class() == RegClass::Float {
236+
Inst::FpuLoad {
237+
size: FpuSize::F64,
238+
rd: into_reg,
239+
mem,
240+
}
241+
} else {
242+
Inst::Load {
243+
rt: into_reg,
244+
mem,
245+
kind: LoadKind::Word,
246+
}
238247
}
239248
}
240249

241250
fn gen_store_base_offset(base: Reg, offset: i32, from_reg: Reg, _ty: Type) -> Inst {
242-
Inst::Store {
243-
rt: from_reg,
244-
mem: AMode::RegOffset { rn: base, offset },
245-
kind: StoreKind::Word,
251+
let mem = AMode::RegOffset { rn: base, offset };
252+
if from_reg.class() == RegClass::Float {
253+
Inst::FpuStore {
254+
size: FpuSize::F64,
255+
rt: from_reg,
256+
mem,
257+
}
258+
} else {
259+
Inst::Store {
260+
rt: from_reg,
261+
mem,
262+
kind: StoreKind::Word,
263+
}
246264
}
247265
}
248266

@@ -322,27 +340,32 @@ impl ABIMachineSpec for Arm32MachineDeps {
322340
+ frame_layout.outgoing_args_size;
323341
if stack_size > 0 {
324342
insts.extend(Self::gen_sp_reg_adjust(-(stack_size as i32)));
325-
let mut cur_offset = 0i32;
343+
// Callee-saved registers are saved at the *top* of the frame, above
344+
// the fixed stack-slot and outgoing-argument regions (which resolve
345+
// from sp upward). Placing them from sp=0 would alias those regions
346+
// and corrupt saved registers.
347+
let mut cur_offset = 0u32;
326348
for reg in &frame_layout.clobbered_callee_saves {
327349
let r = Reg::from(reg.to_reg());
350+
let bytes = if r.class() == RegClass::Float { 8 } else { 4 };
351+
cur_offset = align_to(cur_offset, bytes);
328352
let mem = AMode::SPOffset {
329-
offset: i64::from(cur_offset),
353+
offset: i64::from(stack_size - cur_offset - bytes),
330354
};
331355
if r.class() == RegClass::Float {
332356
insts.push(Inst::FpuStore {
333357
size: FpuSize::F64,
334358
rt: r,
335359
mem,
336360
});
337-
cur_offset += 8;
338361
} else {
339362
insts.push(Inst::Store {
340363
rt: r,
341364
mem,
342365
kind: StoreKind::Word,
343366
});
344-
cur_offset += 4;
345367
}
368+
cur_offset += bytes;
346369
}
347370
}
348371
insts
@@ -357,26 +380,29 @@ impl ABIMachineSpec for Arm32MachineDeps {
357380
let stack_size = frame_layout.clobber_size
358381
+ frame_layout.fixed_frame_storage_size
359382
+ frame_layout.outgoing_args_size;
360-
let mut cur_offset = 0i32;
383+
// Mirror the top-of-frame placement used by `gen_clobber_save`.
384+
let mut cur_offset = 0u32;
361385
for reg in &frame_layout.clobbered_callee_saves {
386+
let is_float = reg.to_reg().class() == RegClass::Float;
387+
let bytes = if is_float { 8 } else { 4 };
388+
cur_offset = align_to(cur_offset, bytes);
362389
let mem = AMode::SPOffset {
363-
offset: i64::from(cur_offset),
390+
offset: i64::from(stack_size - cur_offset - bytes),
364391
};
365-
if reg.to_reg().class() == RegClass::Float {
392+
if is_float {
366393
insts.push(Inst::FpuLoad {
367394
size: FpuSize::F64,
368395
rd: reg.map(Reg::from),
369396
mem,
370397
});
371-
cur_offset += 8;
372398
} else {
373399
insts.push(Inst::Load {
374400
rt: reg.map(Reg::from),
375401
mem,
376402
kind: LoadKind::Word,
377403
});
378-
cur_offset += 4;
379404
}
405+
cur_offset += bytes;
380406
}
381407
if stack_size > 0 {
382408
insts.extend(Self::gen_sp_reg_adjust(stack_size as i32));

cranelift/codegen/src/isa/arm32/inst.isle

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,10 @@
304304
;; A permanently-undefined instruction used to encode a trap.
305305
(Udf (code TrapCode))
306306

307+
;; Conditionally trap on the current flags: emitted as
308+
;; `b<!cond> 1f; udf <code>; 1:`.
309+
(TrapIf (cond Cond) (code TrapCode))
310+
307311
;; Load/store multiple, increment-after (`ldmia`/`stmia rn{!}, {list}`).
308312
(LdmStm (load bool) (rn Reg) (writeback bool) (reg_list u32))
309313

@@ -534,17 +538,12 @@
534538
(decl pure use_idiv () bool)
535539
(extern constructor use_idiv use_idiv)
536540

537-
(decl sdiv_reg (Reg Reg) Reg)
538-
(rule (sdiv_reg rn rm)
539-
(let ((rd WritableReg (temp_writable_reg $I32))
540-
(_ Unit (emit (MInst.SDiv rd rn rm))))
541-
rd))
542-
543-
(decl udiv_reg (Reg Reg) Reg)
544-
(rule (udiv_reg rn rm)
545-
(let ((rd WritableReg (temp_writable_reg $I32))
546-
(_ Unit (emit (MInst.UDiv rd rn rm))))
547-
rd))
541+
;; Signed/unsigned divide with the Cranelift-mandated trap checks (divide by
542+
;; zero, and `INT_MIN / -1` overflow for `sdiv`) emitted before the divide.
543+
(decl gen_sdiv (Reg Reg) Reg)
544+
(extern constructor gen_sdiv gen_sdiv)
545+
(decl gen_udiv (Reg Reg) Reg)
546+
(extern constructor gen_udiv gen_udiv)
548547

549548
;;;; Bit operations and extends ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
550549

@@ -699,6 +698,19 @@
699698
(decl cond_from_floatcc (FloatCC) Cond)
700699
(extern constructor cond_from_floatcc cond_from_floatcc)
701700

701+
;; The two float conditions that a single ARM condition can't express, built as
702+
;; a 0/1 boolean from the flags left by a `vcmp`. `one` (ordered and not-equal)
703+
;; keeps the not-equal result only when ordered (`vc`); `ueq` (unordered or
704+
;; equal) forces the equal result to 1 when unordered (`vs`).
705+
(decl gen_fcmp_one (FpuSize Reg Reg) Reg)
706+
(rule (gen_fcmp_one size a b)
707+
(let ((_ Unit (emit_side_effect (vcmp size a b))))
708+
(csel (Cond.Vc) (csetv (Cond.Ne)) (gen_constant 0))))
709+
(decl gen_fcmp_ueq (FpuSize Reg Reg) Reg)
710+
(rule (gen_fcmp_ueq size a b)
711+
(let ((_ Unit (emit_side_effect (vcmp size a b))))
712+
(csel (Cond.Vs) (gen_constant 1) (csetv (Cond.Eq)))))
713+
702714
;; `fcopysign` via GPR bit manipulation (clear the sign of `a`, OR in `b`'s).
703715
(decl gen_copysign_f32 (Reg Reg) Reg)
704716
(extern constructor gen_copysign_f32 gen_copysign_f32)
@@ -943,6 +955,12 @@
943955
(decl cond_from_intcc (IntCC) Cond)
944956
(extern constructor cond_from_intcc cond_from_intcc)
945957

958+
;; Emit a `cmp` of two (possibly narrow) integer values as a side effect,
959+
;; widening i8/i16 operands to 32 bits per the comparison's signedness, and
960+
;; return the ARM condition that then tests the result.
961+
(decl emit_icmp_cmp (IntCC Value Value) Cond)
962+
(extern constructor emit_icmp_cmp emit_icmp_cmp)
963+
946964
;; Emit the compare sequence for a 64-bit `icmp` and return the ARM condition
947965
;; that then tests it (the compare itself is emitted as a side effect).
948966
(decl lower_icmp_i64 (IntCC ValueRegs ValueRegs) Cond)

cranelift/codegen/src/isa/arm32/inst/emit.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,15 @@ impl MachInstEmit for Inst {
748748
sink.add_trap(*code);
749749
put_u32(sink, enc_udf());
750750
}
751+
Inst::TrapIf { cond, code } => {
752+
// Branch over the trap when the condition does not hold.
753+
let skip = sink.get_label();
754+
sink.use_label_at_offset(sink.cur_offset(), skip, LabelUse::Branch26);
755+
put_u32(sink, enc_bcond(cond.invert(), 0));
756+
sink.add_trap(*code);
757+
put_u32(sink, enc_udf());
758+
sink.bind_label(skip, state.ctrl_plane_mut());
759+
}
751760
Inst::LdmStm {
752761
load,
753762
rn,

cranelift/codegen/src/isa/arm32/inst/mod.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ fn arm32_get_operands(inst: &mut Inst, collector: &mut impl OperandVisitor) {
153153
use_if_virtual(collector, rm);
154154
def_if_virtual(collector, rd);
155155
}
156-
Inst::Udf { .. } | Inst::Barrier { .. } => {}
156+
Inst::Udf { .. } | Inst::TrapIf { .. } | Inst::Barrier { .. } => {}
157157

158158
Inst::AtomicRmw {
159159
rd,
@@ -551,9 +551,12 @@ impl MachInst for Inst {
551551
}
552552

553553
fn worst_case_size() -> CodeOffset {
554-
// The largest instruction is `MovImm`, which expands to a `movw` plus a
555-
// `movt`: 8 bytes.
556-
8
554+
// Must be an upper bound on the number of bytes any single (non
555+
// jump-table) instruction emits, since the buffer uses it as the
556+
// per-instruction island lookahead. The largest such sequence is the
557+
// 64-bit atomic min/max RMW loop, which expands to 10 words (40 bytes).
558+
// Inline jump tables manage their own islands and are exempt.
559+
44
557560
}
558561

559562
fn worst_case_island_growth() -> CodeOffset {
@@ -771,6 +774,9 @@ impl Inst {
771774
alloc::format!("rrx {}, {}", r(rd.to_reg()), r(*rm))
772775
}
773776
Inst::Udf { code } => alloc::format!("udf ; {code}"),
777+
Inst::TrapIf { cond, code } => {
778+
alloc::format!("b{} 1f ; udf ; 1: ; {code}", cond.invert().name())
779+
}
774780
Inst::LdmStm {
775781
load,
776782
rn,

cranelift/codegen/src/isa/arm32/lower.isle

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,17 @@
208208
(let ((_ Unit (emit_side_effect (vcmp (FpuSize.F64) (put_in_reg a) (put_in_reg b)))))
209209
(csetv (cond_from_floatcc cc))))
210210

211+
;; `one` (ordered and not-equal) and `ueq` (unordered or equal) can't be
212+
;; expressed as a single ARM condition, so they get a two-condition sequence.
213+
(rule 1 (lower (fcmp _ (FloatCC.OrderedNotEqual) a @ (value_type $F32) b))
214+
(gen_fcmp_one (FpuSize.F32) (put_in_reg a) (put_in_reg b)))
215+
(rule 1 (lower (fcmp _ (FloatCC.OrderedNotEqual) a @ (value_type $F64) b))
216+
(gen_fcmp_one (FpuSize.F64) (put_in_reg a) (put_in_reg b)))
217+
(rule 1 (lower (fcmp _ (FloatCC.UnorderedOrEqual) a @ (value_type $F32) b))
218+
(gen_fcmp_ueq (FpuSize.F32) (put_in_reg a) (put_in_reg b)))
219+
(rule 1 (lower (fcmp _ (FloatCC.UnorderedOrEqual) a @ (value_type $F64) b))
220+
(gen_fcmp_ueq (FpuSize.F64) (put_in_reg a) (put_in_reg b)))
221+
211222
;; f32 <-> f64.
212223
(rule (lower (fpromote $F64 x))
213224
(vcvt_ff true (put_in_reg x)))
@@ -266,14 +277,15 @@
266277
(mov_to_fpu64 (put_in_regs x)))
267278

268279
;;;; Divides (only when hardware `sdiv`/`udiv` is available) ;;;;;;;;;;;;;;;;;;;
269-
;; NOTE: trap-on-zero / INT_MIN overflow checks are not yet emitted.
280+
;; `gen_sdiv`/`gen_udiv` emit the trap-on-zero check (and, for `sdiv`, the
281+
;; `INT_MIN / -1` overflow check) before the divide.
270282

271283
(rule (lower (sdiv $I32 x y))
272284
(if-let true (use_idiv))
273-
(sdiv_reg (put_in_reg x) (put_in_reg y)))
285+
(gen_sdiv (put_in_reg x) (put_in_reg y)))
274286
(rule (lower (udiv $I32 x y))
275287
(if-let true (use_idiv))
276-
(udiv_reg (put_in_reg x) (put_in_reg y)))
288+
(gen_udiv (put_in_reg x) (put_in_reg y)))
277289

278290
;;;; Counting / byte-swap / bit-reverse ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
279291

@@ -304,10 +316,11 @@
304316

305317
;;;; `icmp` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
306318

307-
;; 32-bit comparison producing a 0/1 boolean.
319+
;; 32-bit comparison producing a 0/1 boolean. `emit_icmp_cmp` widens narrow
320+
;; (i8/i16) operands to 32 bits per the comparison's signedness before the
321+
;; `cmp`, since Cranelift leaves the high bits of narrow values undefined.
308322
(rule (lower (icmp _ cc a @ (value_type (fits_in_32 _)) b))
309-
(let ((_ Unit (emit_side_effect (cmp_rr (put_in_reg a) (put_in_reg b)))))
310-
(csetv (cond_from_intcc cc))))
323+
(csetv (emit_icmp_cmp cc a b)))
311324

312325
;; 64-bit comparison producing a 0/1 boolean.
313326
(rule 4 (lower (icmp _ cc a @ (value_type $I64) b))
@@ -317,14 +330,23 @@
317330

318331
;; Fuse an `icmp` condition into the compare that precedes the select.
319332
(rule 1 (lower (select (fits_in_32 _) (icmp _ cc x @ (value_type (fits_in_32 _)) y) a b))
320-
(let ((_ Unit (emit_side_effect (cmp_rr (put_in_reg x) (put_in_reg y)))))
321-
(csel (cond_from_intcc cc) (put_in_reg a) (put_in_reg b))))
333+
(let ((cond Cond (emit_icmp_cmp cc x y)))
334+
(csel cond (put_in_reg a) (put_in_reg b))))
322335

323336
;; Generic select: branch on whether the condition value is non-zero.
324337
(rule (lower (select (fits_in_32 _) c a b))
325338
(let ((_ Unit (emit_side_effect (cmp_imm (put_in_reg c) 0))))
326339
(csel (Cond.Ne) (put_in_reg a) (put_in_reg b))))
327340

341+
;; 64-bit select: pick each half of the pair. The operands are materialized
342+
;; before the compare so nothing clobbers the flags in between.
343+
(rule 4 (lower (select $I64 c a b))
344+
(let ((av ValueRegs (put_in_regs a))
345+
(bv ValueRegs (put_in_regs b))
346+
(_ Unit (emit_side_effect (cmp_imm (put_in_reg c) 0))))
347+
(value_regs (csel (Cond.Ne) (vr_lo av) (vr_lo bv))
348+
(csel (Cond.Ne) (vr_hi av) (vr_hi bv)))))
349+
328350
;;;; Shifts: `ishl` / `ushr` / `sshr` / `rotr` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
329351

330352
(rule 1 (lower (ishl $I32 x (iconst _ (u64_from_imm64 n))))
@@ -349,6 +371,12 @@
349371

350372
;;;; Loads ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
351373

374+
;; A plain `load` reads exactly `sizeof(ty)` bytes; narrow types zero-extend
375+
;; into the 32-bit result register.
376+
(rule 1 (lower (load $I8 flags addr offset))
377+
(arm_load (amode addr offset) (LoadKind.UByte)))
378+
(rule 1 (lower (load $I16 flags addr offset))
379+
(arm_load (amode addr offset) (LoadKind.UHalf)))
352380
(rule (lower (load (fits_in_32 _) flags addr offset))
353381
(arm_load (amode addr offset) (LoadKind.Word)))
354382

@@ -363,6 +391,11 @@
363391

364392
;;;; Stores ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
365393

394+
;; A plain `store` writes exactly `sizeof(ty)` bytes.
395+
(rule 1 (lower (store flags val @ (value_type $I8) addr offset))
396+
(arm_store (put_in_reg val) (amode addr offset) (StoreKind.Byte)))
397+
(rule 1 (lower (store flags val @ (value_type $I16) addr offset))
398+
(arm_store (put_in_reg val) (amode addr offset) (StoreKind.Half)))
366399
(rule (lower (store flags val @ (value_type (fits_in_32 _)) addr offset))
367400
(arm_store (put_in_reg val) (amode addr offset) (StoreKind.Word)))
368401

@@ -431,16 +464,33 @@
431464

432465
;; Fuse a 32-bit `icmp` feeding a `brif` into a compare plus conditional branch.
433466
(rule 1 (lower_branch (brif (icmp _ cc a @ (value_type (fits_in_32 _)) b) _ _) (two_targets taken not_taken))
434-
(emit_side_effect
435-
(side_effect_concat
436-
(cmp_rr (put_in_reg a) (put_in_reg b))
437-
(cond_br (cond_from_intcc cc) taken not_taken))))
467+
(emit_side_effect (cond_br (emit_icmp_cmp cc a b) taken not_taken)))
438468

439469
;; Fuse a 64-bit `icmp` feeding a `brif`.
440470
(rule 2 (lower_branch (brif (icmp _ cc a @ (value_type $I64) b) _ _) (two_targets taken not_taken))
441471
(emit_side_effect
442472
(cond_br (lower_icmp_i64 cc (put_in_regs a) (put_in_regs b)) taken not_taken)))
443473

474+
;; `one`/`ueq` feeding a `brif`: materialize the two-condition boolean, then
475+
;; branch on whether it is non-zero (priority 2 beats the generic fused rule,
476+
;; whose single-condition mapping can't represent these).
477+
(rule 2 (lower_branch (brif (fcmp _ (FloatCC.OrderedNotEqual) a @ (value_type $F32) b) _ _) (two_targets taken not_taken))
478+
(emit_side_effect (side_effect_concat
479+
(cmp_imm (gen_fcmp_one (FpuSize.F32) (put_in_reg a) (put_in_reg b)) 0)
480+
(cond_br (Cond.Ne) taken not_taken))))
481+
(rule 2 (lower_branch (brif (fcmp _ (FloatCC.OrderedNotEqual) a @ (value_type $F64) b) _ _) (two_targets taken not_taken))
482+
(emit_side_effect (side_effect_concat
483+
(cmp_imm (gen_fcmp_one (FpuSize.F64) (put_in_reg a) (put_in_reg b)) 0)
484+
(cond_br (Cond.Ne) taken not_taken))))
485+
(rule 2 (lower_branch (brif (fcmp _ (FloatCC.UnorderedOrEqual) a @ (value_type $F32) b) _ _) (two_targets taken not_taken))
486+
(emit_side_effect (side_effect_concat
487+
(cmp_imm (gen_fcmp_ueq (FpuSize.F32) (put_in_reg a) (put_in_reg b)) 0)
488+
(cond_br (Cond.Ne) taken not_taken))))
489+
(rule 2 (lower_branch (brif (fcmp _ (FloatCC.UnorderedOrEqual) a @ (value_type $F64) b) _ _) (two_targets taken not_taken))
490+
(emit_side_effect (side_effect_concat
491+
(cmp_imm (gen_fcmp_ueq (FpuSize.F64) (put_in_reg a) (put_in_reg b)) 0)
492+
(cond_br (Cond.Ne) taken not_taken))))
493+
444494
;; Fuse an `fcmp` feeding a `brif`.
445495
(rule 1 (lower_branch (brif (fcmp _ cc a @ (value_type $F32) b) _ _) (two_targets taken not_taken))
446496
(emit_side_effect

0 commit comments

Comments
 (0)