Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/cranelift/codegen/src/isa/aarch64/pcc.rs
1693 views
1
//! Proof-carrying code checking for AArch64 VCode.
2
3
use crate::ir::MemFlags;
4
use crate::ir::pcc::*;
5
use crate::ir::types::*;
6
use crate::isa::aarch64::inst::Inst;
7
use crate::isa::aarch64::inst::args::{Cond, PairAMode, ShiftOp};
8
use crate::isa::aarch64::inst::regs::zero_reg;
9
use crate::isa::aarch64::inst::{ALUOp, MoveWideOp};
10
use crate::isa::aarch64::inst::{AMode, ExtendOp};
11
use crate::machinst::Reg;
12
use crate::machinst::pcc::*;
13
use crate::machinst::{InsnIndex, VCode};
14
use crate::trace;
15
16
fn extend_fact(ctx: &FactContext, value: &Fact, mode: ExtendOp) -> Option<Fact> {
17
match mode {
18
ExtendOp::UXTB => ctx.uextend(value, 8, 64),
19
ExtendOp::UXTH => ctx.uextend(value, 16, 64),
20
ExtendOp::UXTW => ctx.uextend(value, 32, 64),
21
ExtendOp::UXTX => Some(value.clone()),
22
ExtendOp::SXTB => ctx.sextend(value, 8, 64),
23
ExtendOp::SXTH => ctx.sextend(value, 16, 64),
24
ExtendOp::SXTW => ctx.sextend(value, 32, 64),
25
ExtendOp::SXTX => None,
26
}
27
}
28
29
/// Flow-state between facts.
30
#[derive(Clone, Debug, Default)]
31
pub struct FactFlowState {
32
cmp_flags: Option<(Fact, Fact)>,
33
}
34
35
pub(crate) fn check(
36
ctx: &FactContext,
37
vcode: &mut VCode<Inst>,
38
inst_idx: InsnIndex,
39
state: &mut FactFlowState,
40
) -> PccResult<()> {
41
let inst = &vcode[inst_idx];
42
trace!("Checking facts on inst: {:?}", inst);
43
44
// We only persist flag state for one instruction, because we
45
// can't exhaustively enumerate all flags-effecting ops; so take
46
// the `cmp_state` here and perhaps use it below but don't let it
47
// remain.
48
let cmp_flags = state.cmp_flags.take();
49
trace!(" * with cmp_flags = {cmp_flags:?}");
50
51
match *inst {
52
Inst::Args { .. } => {
53
// Defs on the args have "axiomatic facts": we trust the
54
// ABI code to pass through the values unharmed, so the
55
// facts given to us in the CLIF should still be true.
56
Ok(())
57
}
58
Inst::ULoad8 { rd, ref mem, flags }
59
| Inst::SLoad8 { rd, ref mem, flags }
60
| Inst::ULoad16 { rd, ref mem, flags }
61
| Inst::SLoad16 { rd, ref mem, flags }
62
| Inst::ULoad32 { rd, ref mem, flags }
63
| Inst::SLoad32 { rd, ref mem, flags }
64
| Inst::ULoad64 { rd, ref mem, flags } => {
65
let access_ty = inst.mem_type().unwrap();
66
check_load(ctx, Some(rd.to_reg()), flags, mem, vcode, access_ty)
67
}
68
Inst::FpuLoad16 { ref mem, flags, .. }
69
| Inst::FpuLoad32 { ref mem, flags, .. }
70
| Inst::FpuLoad64 { ref mem, flags, .. }
71
| Inst::FpuLoad128 { ref mem, flags, .. } => {
72
let access_ty = inst.mem_type().unwrap();
73
check_load(ctx, None, flags, mem, vcode, access_ty)
74
}
75
Inst::LoadP64 { ref mem, flags, .. } => check_load_pair(ctx, flags, mem, vcode, 16),
76
Inst::FpuLoadP64 { ref mem, flags, .. } => check_load_pair(ctx, flags, mem, vcode, 16),
77
Inst::FpuLoadP128 { ref mem, flags, .. } => check_load_pair(ctx, flags, mem, vcode, 32),
78
Inst::VecLoadReplicate {
79
rn, flags, size, ..
80
} => check_load_addr(ctx, flags, rn, vcode, size.lane_size().ty()),
81
Inst::LoadAcquire {
82
access_ty,
83
rn,
84
flags,
85
..
86
} => check_load_addr(ctx, flags, rn, vcode, access_ty),
87
88
Inst::Store8 { rd, ref mem, flags }
89
| Inst::Store16 { rd, ref mem, flags }
90
| Inst::Store32 { rd, ref mem, flags }
91
| Inst::Store64 { rd, ref mem, flags } => {
92
let access_ty = inst.mem_type().unwrap();
93
check_store(ctx, Some(rd), flags, mem, vcode, access_ty)
94
}
95
Inst::FpuStore16 { ref mem, flags, .. }
96
| Inst::FpuStore32 { ref mem, flags, .. }
97
| Inst::FpuStore64 { ref mem, flags, .. }
98
| Inst::FpuStore128 { ref mem, flags, .. } => {
99
let access_ty = inst.mem_type().unwrap();
100
check_store(ctx, None, flags, mem, vcode, access_ty)
101
}
102
Inst::StoreP64 { ref mem, flags, .. } => check_store_pair(ctx, flags, mem, vcode, 16),
103
Inst::FpuStoreP64 { ref mem, flags, .. } => check_store_pair(ctx, flags, mem, vcode, 16),
104
Inst::FpuStoreP128 { ref mem, flags, .. } => check_store_pair(ctx, flags, mem, vcode, 32),
105
Inst::StoreRelease {
106
access_ty,
107
rn,
108
flags,
109
..
110
} => check_store_addr(ctx, flags, rn, vcode, access_ty),
111
112
Inst::AluRRR {
113
alu_op: ALUOp::Add | ALUOp::AddS,
114
size,
115
rd,
116
rn,
117
rm,
118
} => check_binop(ctx, vcode, 64, rd, rn, rm, |rn, rm| {
119
clamp_range(
120
ctx,
121
64,
122
size.bits().into(),
123
ctx.add(rn, rm, size.bits().into()),
124
)
125
}),
126
Inst::AluRRImm12 {
127
alu_op: ALUOp::Add | ALUOp::AddS,
128
size,
129
rd,
130
rn,
131
imm12,
132
} => check_unop(ctx, vcode, 64, rd, rn, |rn| {
133
let imm12: i64 = imm12.value().into();
134
clamp_range(
135
ctx,
136
64,
137
size.bits().into(),
138
ctx.offset(&rn, size.bits().into(), imm12),
139
)
140
}),
141
Inst::AluRRImm12 {
142
alu_op: ALUOp::Sub,
143
size,
144
rd,
145
rn,
146
imm12,
147
} => check_unop(ctx, vcode, 64, rd, rn, |rn| {
148
let imm12: i64 = imm12.value().into();
149
clamp_range(
150
ctx,
151
64,
152
size.bits().into(),
153
ctx.offset(&rn, size.bits().into(), -imm12),
154
)
155
}),
156
Inst::AluRRR {
157
alu_op: ALUOp::Sub,
158
size,
159
rd,
160
rn,
161
rm,
162
} => check_binop(ctx, vcode, 64, rd, rn, rm, |rn, rm| {
163
if let Some(k) = rm.as_const(64) {
164
clamp_range(
165
ctx,
166
64,
167
size.bits().into(),
168
ctx.offset(rn, size.bits().into(), -(k as i64)),
169
)
170
} else {
171
clamp_range(ctx, 64, size.bits().into(), None)
172
}
173
}),
174
Inst::AluRRRShift {
175
alu_op: ALUOp::Add | ALUOp::AddS,
176
size,
177
rd,
178
rn,
179
rm,
180
shiftop,
181
} if shiftop.op() == ShiftOp::LSL && has_fact(vcode, rn) && has_fact(vcode, rm) => {
182
check_binop(ctx, vcode, 64, rd, rn, rm, |rn, rm| {
183
let rm_shifted = fail_if_missing(ctx.shl(
184
&rm,
185
size.bits().into(),
186
shiftop.amt().value().into(),
187
))?;
188
clamp_range(
189
ctx,
190
64,
191
size.bits().into(),
192
ctx.add(&rn, &rm_shifted, size.bits().into()),
193
)
194
})
195
}
196
Inst::AluRRRExtend {
197
alu_op: ALUOp::Add | ALUOp::AddS,
198
size,
199
rd,
200
rn,
201
rm,
202
extendop,
203
} if has_fact(vcode, rn) && has_fact(vcode, rm) => {
204
check_binop(ctx, vcode, 64, rd, rn, rm, |rn, rm| {
205
let rm_extended = fail_if_missing(extend_fact(ctx, rm, extendop))?;
206
clamp_range(
207
ctx,
208
64,
209
size.bits().into(),
210
ctx.add(&rn, &rm_extended, size.bits().into()),
211
)
212
})
213
}
214
Inst::AluRRImmShift {
215
alu_op: ALUOp::Lsl,
216
size,
217
rd,
218
rn,
219
immshift,
220
} if has_fact(vcode, rn) => check_unop(ctx, vcode, 64, rd, rn, |rn| {
221
clamp_range(
222
ctx,
223
64,
224
size.bits().into(),
225
ctx.shl(&rn, size.bits().into(), immshift.value().into()),
226
)
227
}),
228
Inst::Extend {
229
rd,
230
rn,
231
signed: false,
232
from_bits,
233
to_bits,
234
} if has_fact(vcode, rn) => check_unop(ctx, vcode, 64, rd, rn, |rn| {
235
clamp_range(
236
ctx,
237
64,
238
to_bits.into(),
239
ctx.uextend(&rn, from_bits.into(), to_bits.into()),
240
)
241
}),
242
243
Inst::AluRRR {
244
alu_op: ALUOp::SubS,
245
size,
246
rd,
247
rn,
248
rm,
249
} if rd.to_reg() == zero_reg() => {
250
// Compare.
251
let rn = get_fact_or_default(vcode, rn, size.bits().into());
252
let rm = get_fact_or_default(vcode, rm, size.bits().into());
253
state.cmp_flags = Some((rn, rm));
254
Ok(())
255
}
256
257
Inst::AluRRImmLogic {
258
alu_op: ALUOp::Orr,
259
size,
260
rd,
261
rn,
262
imml,
263
} if rn == zero_reg() => {
264
let constant = imml.value();
265
check_constant(ctx, vcode, rd, size.bits().into(), constant)
266
}
267
268
Inst::AluRRR { rd, size, .. }
269
| Inst::AluRRImm12 { rd, size, .. }
270
| Inst::AluRRRShift { rd, size, .. }
271
| Inst::AluRRRExtend { rd, size, .. }
272
| Inst::AluRRImmLogic { rd, size, .. }
273
| Inst::AluRRImmShift { rd, size, .. } => check_output(ctx, vcode, rd, &[], |_vcode| {
274
clamp_range(ctx, 64, size.bits().into(), None)
275
}),
276
277
Inst::Extend {
278
rd,
279
from_bits,
280
to_bits,
281
..
282
} => check_output(ctx, vcode, rd, &[], |_vcode| {
283
clamp_range(ctx, to_bits.into(), from_bits.into(), None)
284
}),
285
286
Inst::MovWide {
287
op: MoveWideOp::MovZ,
288
imm,
289
size: _,
290
rd,
291
} => {
292
let constant = u64::from(imm.bits) << (imm.shift * 16);
293
check_constant(ctx, vcode, rd, 64, constant)
294
}
295
296
Inst::MovWide {
297
op: MoveWideOp::MovN,
298
imm,
299
size,
300
rd,
301
} => {
302
let constant = !(u64::from(imm.bits) << (imm.shift * 16)) & size.max_value();
303
check_constant(ctx, vcode, rd, 64, constant)
304
}
305
306
Inst::MovK { rd, rn, imm, .. } => {
307
let input = get_fact_or_default(vcode, rn, 64);
308
if let Some(input_constant) = input.as_const(64) {
309
let mask = 0xffff << (imm.shift * 16);
310
let constant = u64::from(imm.bits) << (imm.shift * 16);
311
let constant = (input_constant & !mask) | constant;
312
check_constant(ctx, vcode, rd, 64, constant)
313
} else {
314
check_output(ctx, vcode, rd, &[], |_vcode| {
315
Ok(Some(Fact::max_range_for_width(64)))
316
})
317
}
318
}
319
320
Inst::CSel { rd, cond, rn, rm }
321
if (cond == Cond::Hs || cond == Cond::Hi) && cmp_flags.is_some() =>
322
{
323
let (cmp_lhs, cmp_rhs) = cmp_flags.unwrap();
324
trace!("CSel: cmp {cond:?} ({cmp_lhs:?}, {cmp_rhs:?})");
325
326
check_output(ctx, vcode, rd, &[], |vcode| {
327
// We support transitivity-based reasoning. If the
328
// comparison establishes that
329
//
330
// (x+K1) <= (y+K2)
331
//
332
// then on the true-side of the select we can edit the maximum
333
// in a DynamicMem or DynamicRange by replacing x's with y's
334
// with appropriate offsets -- this widens the range.
335
//
336
// Likewise, on the false-side of the select we can
337
// replace y's with x's -- this also widens the range. On
338
// the false side we know the inequality is strict, so we
339
// can offset by one.
340
341
// True side: lhs >= rhs (Hs) or lhs > rhs (Hi).
342
let rn = get_fact_or_default(vcode, rn, 64);
343
let lhs_kind = match cond {
344
Cond::Hs => InequalityKind::Loose,
345
Cond::Hi => InequalityKind::Strict,
346
_ => unreachable!(),
347
};
348
let rn = ctx.apply_inequality(&rn, &cmp_lhs, &cmp_rhs, lhs_kind);
349
// false side: rhs < lhs (Hs) or rhs <= lhs (Hi).
350
let rm = get_fact_or_default(vcode, rm, 64);
351
let rhs_kind = match cond {
352
Cond::Hs => InequalityKind::Strict,
353
Cond::Hi => InequalityKind::Loose,
354
_ => unreachable!(),
355
};
356
let rm = ctx.apply_inequality(&rm, &cmp_rhs, &cmp_lhs, rhs_kind);
357
let union = ctx.union(&rn, &rm);
358
// Union the two facts.
359
clamp_range(ctx, 64, 64, union)
360
})
361
}
362
363
_ if vcode.inst_defines_facts(inst_idx) => Err(PccError::UnsupportedFact),
364
365
_ => Ok(()),
366
}
367
}
368
369
fn check_load(
370
ctx: &FactContext,
371
rd: Option<Reg>,
372
flags: MemFlags,
373
addr: &AMode,
374
vcode: &VCode<Inst>,
375
ty: Type,
376
) -> PccResult<()> {
377
let result_fact = rd.and_then(|rd| vcode.vreg_fact(rd.into()));
378
let bits = u16::try_from(ty.bits()).unwrap();
379
check_addr(
380
ctx,
381
flags,
382
addr,
383
vcode,
384
ty,
385
LoadOrStore::Load {
386
result_fact,
387
from_bits: bits,
388
to_bits: bits,
389
},
390
)
391
}
392
393
fn check_store(
394
ctx: &FactContext,
395
rd: Option<Reg>,
396
flags: MemFlags,
397
addr: &AMode,
398
vcode: &VCode<Inst>,
399
ty: Type,
400
) -> PccResult<()> {
401
let stored_fact = rd.and_then(|rd| vcode.vreg_fact(rd.into()));
402
check_addr(
403
ctx,
404
flags,
405
addr,
406
vcode,
407
ty,
408
LoadOrStore::Store { stored_fact },
409
)
410
}
411
412
fn check_addr<'a>(
413
ctx: &FactContext,
414
flags: MemFlags,
415
addr: &AMode,
416
vcode: &VCode<Inst>,
417
ty: Type,
418
op: LoadOrStore<'a>,
419
) -> PccResult<()> {
420
if !flags.checked() {
421
return Ok(());
422
}
423
424
trace!("check_addr: {:?}", addr);
425
426
let check = |addr: &Fact, ty: Type| -> PccResult<()> {
427
match op {
428
LoadOrStore::Load {
429
result_fact,
430
from_bits,
431
to_bits,
432
} => {
433
let loaded_fact =
434
clamp_range(ctx, to_bits, from_bits, ctx.load(addr, ty)?.cloned())?;
435
trace!(
436
"checking a load: loaded_fact = {loaded_fact:?} result_fact = {result_fact:?}"
437
);
438
if ctx.subsumes_fact_optionals(loaded_fact.as_ref(), result_fact) {
439
Ok(())
440
} else {
441
Err(PccError::UnsupportedFact)
442
}
443
}
444
LoadOrStore::Store { stored_fact } => ctx.store(addr, ty, stored_fact),
445
}
446
};
447
448
match addr {
449
&AMode::RegReg { rn, rm } => {
450
let rn = get_fact_or_default(vcode, rn, 64);
451
let rm = get_fact_or_default(vcode, rm, 64);
452
let sum = fail_if_missing(ctx.add(&rn, &rm, 64))?;
453
trace!("rn = {rn:?} rm = {rm:?} sum = {sum:?}");
454
check(&sum, ty)
455
}
456
&AMode::RegScaled { rn, rm } => {
457
let rn = get_fact_or_default(vcode, rn, 64);
458
let rm = get_fact_or_default(vcode, rm, 64);
459
let rm_scaled = fail_if_missing(ctx.scale(&rm, 64, ty.bytes()))?;
460
let sum = fail_if_missing(ctx.add(&rn, &rm_scaled, 64))?;
461
check(&sum, ty)
462
}
463
&AMode::RegScaledExtended { rn, rm, extendop } => {
464
let rn = get_fact_or_default(vcode, rn, 64);
465
let rm = get_fact_or_default(vcode, rm, 64);
466
let rm_extended = fail_if_missing(extend_fact(ctx, &rm, extendop))?;
467
let rm_scaled = fail_if_missing(ctx.scale(&rm_extended, 64, ty.bytes()))?;
468
let sum = fail_if_missing(ctx.add(&rn, &rm_scaled, 64))?;
469
check(&sum, ty)
470
}
471
&AMode::RegExtended { rn, rm, extendop } => {
472
let rn = get_fact_or_default(vcode, rn, 64);
473
let rm = get_fact_or_default(vcode, rm, 64);
474
let rm_extended = fail_if_missing(extend_fact(ctx, &rm, extendop))?;
475
let sum = fail_if_missing(ctx.add(&rn, &rm_extended, 64))?;
476
check(&sum, ty)?;
477
Ok(())
478
}
479
&AMode::Unscaled { rn, simm9 } => {
480
let rn = get_fact_or_default(vcode, rn, 64);
481
let sum = fail_if_missing(ctx.offset(&rn, 64, simm9.value.into()))?;
482
check(&sum, ty)
483
}
484
&AMode::UnsignedOffset { rn, uimm12 } => {
485
let rn = get_fact_or_default(vcode, rn, 64);
486
// N.B.: the architecture scales the immediate in the
487
// encoded instruction by the size of the loaded type, so
488
// e.g. an offset field of 4095 can mean a load of offset
489
// 32760 (= 4095 * 8) for I64s. The `UImm12Scaled` type
490
// stores the *scaled* value, so we don't need to multiply
491
// (again) by the type's size here.
492
let offset: u64 = uimm12.value().into();
493
// This `unwrap()` will always succeed because the value
494
// will always be positive and much smaller than
495
// `i64::MAX` (see above).
496
let sum = fail_if_missing(ctx.offset(&rn, 64, i64::try_from(offset).unwrap()))?;
497
check(&sum, ty)
498
}
499
&AMode::Label { .. } | &AMode::Const { .. } => {
500
// Always accept: labels and constants must be within the
501
// generated code (else they won't be resolved).
502
Ok(())
503
}
504
&AMode::RegOffset { rn, off, .. } => {
505
let rn = get_fact_or_default(vcode, rn, 64);
506
let sum = fail_if_missing(ctx.offset(&rn, 64, off))?;
507
check(&sum, ty)
508
}
509
&AMode::SPOffset { .. }
510
| &AMode::FPOffset { .. }
511
| &AMode::IncomingArg { .. }
512
| &AMode::SlotOffset { .. }
513
| &AMode::SPPostIndexed { .. }
514
| &AMode::SPPreIndexed { .. } => {
515
// We trust ABI code (for now!) and no lowering rules
516
// lower input value accesses directly to these.
517
Ok(())
518
}
519
}
520
}
521
522
fn check_load_pair(
523
_ctx: &FactContext,
524
_flags: MemFlags,
525
_addr: &PairAMode,
526
_vcode: &VCode<Inst>,
527
_size: u8,
528
) -> PccResult<()> {
529
Err(PccError::UnimplementedInst)
530
}
531
532
fn check_store_pair(
533
_ctx: &FactContext,
534
_flags: MemFlags,
535
_addr: &PairAMode,
536
_vcode: &VCode<Inst>,
537
_size: u8,
538
) -> PccResult<()> {
539
Err(PccError::UnimplementedInst)
540
}
541
542
fn check_load_addr(
543
ctx: &FactContext,
544
flags: MemFlags,
545
reg: Reg,
546
vcode: &VCode<Inst>,
547
ty: Type,
548
) -> PccResult<()> {
549
if !flags.checked() {
550
return Ok(());
551
}
552
let fact = get_fact_or_default(vcode, reg, 64);
553
let _output_fact = ctx.load(&fact, ty)?;
554
Ok(())
555
}
556
557
fn check_store_addr(
558
ctx: &FactContext,
559
flags: MemFlags,
560
reg: Reg,
561
vcode: &VCode<Inst>,
562
ty: Type,
563
) -> PccResult<()> {
564
if !flags.checked() {
565
return Ok(());
566
}
567
let fact = get_fact_or_default(vcode, reg, 64);
568
let _output_fact = ctx.store(&fact, ty, None)?;
569
Ok(())
570
}
571
572