Trait cranelift_codegen::isa::TargetIsa
source · pub trait TargetIsa: Display + Send + Sync {
Show 14 methods
fn name(&self) -> &'static str;
fn triple(&self) -> &Triple;
fn flags(&self) -> &Flags;
fn machine_env(&self) -> &MachineEnv;
fn isa_flags(&self) -> Vec<Value>;
fn dynamic_vector_bytes(&self, dynamic_ty: Type) -> u32;
fn compile_function(
&self,
func: &Function,
want_disasm: bool
) -> CodegenResult<CompiledCodeBase<Stencil>>;
fn unsigned_add_overflow_condition(&self) -> IntCC;
fn emit_unwind_info(
&self,
result: &CompiledCode,
kind: UnwindInfoKind
) -> CodegenResult<Option<UnwindInfo>>;
fn text_section_builder(
&self,
num_labeled_funcs: usize
) -> Box<dyn TextSectionBuilder>;
fn function_alignment(&self) -> u32;
fn is_branch_protection_enabled(&self) -> bool { ... }
fn map_regalloc_reg_to_dwarf(
&self,
_: Reg
) -> Result<u16, RegisterMappingError> { ... }
fn create_systemv_cie(&self) -> Option<CommonInformationEntry> { ... }
}
Expand description
Methods that are specialized to a target ISA.
Implies a Display trait that shows the shared flags, as well as any ISA-specific flags.
Required Methods§
sourcefn flags(&self) -> &Flags
fn flags(&self) -> &Flags
Get the ISA-independent flags that were used to make this trait object.
sourcefn machine_env(&self) -> &MachineEnv
fn machine_env(&self) -> &MachineEnv
Get the ISA-dependent MachineEnv for managing register allocation.
sourcefn isa_flags(&self) -> Vec<Value>
fn isa_flags(&self) -> Vec<Value>
Get the ISA-dependent flag values that were used to make this trait object.
sourcefn dynamic_vector_bytes(&self, dynamic_ty: Type) -> u32
fn dynamic_vector_bytes(&self, dynamic_ty: Type) -> u32
Get the ISA-dependent maximum vector register size, in bytes.
sourcefn compile_function(
&self,
func: &Function,
want_disasm: bool
) -> CodegenResult<CompiledCodeBase<Stencil>>
fn compile_function(
&self,
func: &Function,
want_disasm: bool
) -> CodegenResult<CompiledCodeBase<Stencil>>
Compile the given function.
sourcefn unsigned_add_overflow_condition(&self) -> IntCC
fn unsigned_add_overflow_condition(&self) -> IntCC
IntCC condition for Unsigned Addition Overflow (Carry).
sourcefn emit_unwind_info(
&self,
result: &CompiledCode,
kind: UnwindInfoKind
) -> CodegenResult<Option<UnwindInfo>>
fn emit_unwind_info(
&self,
result: &CompiledCode,
kind: UnwindInfoKind
) -> CodegenResult<Option<UnwindInfo>>
Creates unwind information for the function.
Returns None
if there is no unwind information for the function.
sourcefn text_section_builder(
&self,
num_labeled_funcs: usize
) -> Box<dyn TextSectionBuilder>
fn text_section_builder(
&self,
num_labeled_funcs: usize
) -> Box<dyn TextSectionBuilder>
Returns an object that can be used to build the text section of an executable.
This object will internally attempt to handle as many relocations as possible using relative calls/jumps/etc between functions.
The num_labeled_funcs
argument here is the number of functions which
will be “labeled” or might have calls between them, typically the number
of defined functions in the object file.
sourcefn function_alignment(&self) -> u32
fn function_alignment(&self) -> u32
The function alignment required by this ISA.
Provided Methods§
sourcefn is_branch_protection_enabled(&self) -> bool
fn is_branch_protection_enabled(&self) -> bool
Get a flag indicating whether branch protection is enabled.
sourcefn map_regalloc_reg_to_dwarf(&self, _: Reg) -> Result<u16, RegisterMappingError>
fn map_regalloc_reg_to_dwarf(&self, _: Reg) -> Result<u16, RegisterMappingError>
Map a regalloc::Reg to its corresponding DWARF register.
sourcefn create_systemv_cie(&self) -> Option<CommonInformationEntry>
fn create_systemv_cie(&self) -> Option<CommonInformationEntry>
Creates a new System V Common Information Entry for the ISA.
Returns None
if the ISA does not support System V unwind information.
Implementations§
source§impl<'a> dyn TargetIsa + 'a
impl<'a> dyn TargetIsa + 'a
Methods implemented for free for target ISA!
sourcepub fn default_call_conv(&self) -> CallConv
pub fn default_call_conv(&self) -> CallConv
Get the default calling convention of this target.
sourcepub fn endianness(&self) -> Endianness
pub fn endianness(&self) -> Endianness
Get the endianness of this ISA.
sourcepub fn code_section_alignment(&self) -> u64
pub fn code_section_alignment(&self) -> u64
Returns the code (text) section alignment for this ISA.
sourcepub fn symbol_alignment(&self) -> u64
pub fn symbol_alignment(&self) -> u64
Returns the minimum symbol alignment for this ISA.
sourcepub fn pointer_type(&self) -> Type
pub fn pointer_type(&self) -> Type
Get the pointer type of this ISA.
Examples found in repository?
More examples
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
fn const_vector_scale(inst: ir::Inst, func: &mut ir::Function, ty: ir::Type, isa: &dyn TargetIsa) {
assert!(ty.bytes() <= 16);
// Use a minimum of 128-bits for the base type.
let base_bytes = std::cmp::max(ty.bytes(), 16);
let scale = (isa.dynamic_vector_bytes(ty) / base_bytes) as i64;
assert!(scale > 0);
let pos = FuncCursor::new(func).at_inst(inst);
pos.func.dfg.replace(inst).iconst(isa.pointer_type(), scale);
}
/// Expand a `global_value` instruction for a vmctx global.
fn vmctx_addr(inst: ir::Inst, func: &mut ir::Function) {
// Get the value representing the `vmctx` argument.
let vmctx = func
.special_param(ir::ArgumentPurpose::VMContext)
.expect("Missing vmctx parameter");
// Replace the `global_value` instruction's value with an alias to the vmctx arg.
let result = func.dfg.first_result(inst);
func.dfg.clear_results(inst);
func.dfg.change_to_alias(result, vmctx);
func.layout.remove_inst(inst);
}
/// Expand a `global_value` instruction for an iadd_imm global.
fn iadd_imm_addr(
inst: ir::Inst,
func: &mut ir::Function,
base: ir::GlobalValue,
offset: i64,
global_type: ir::Type,
) {
let mut pos = FuncCursor::new(func).at_inst(inst);
// Get the value for the lhs. For tidiness, expand VMContext here so that we avoid
// `vmctx_addr` which creates an otherwise unneeded value alias.
let lhs = if let ir::GlobalValueData::VMContext = pos.func.global_values[base] {
pos.func
.special_param(ir::ArgumentPurpose::VMContext)
.expect("Missing vmctx parameter")
} else {
pos.ins().global_value(global_type, base)
};
// Simply replace the `global_value` instruction with an `iadd_imm`, reusing the result value.
pos.func.dfg.replace(inst).iadd_imm(lhs, offset);
}
/// Expand a `global_value` instruction for a load global.
fn load_addr(
inst: ir::Inst,
func: &mut ir::Function,
base: ir::GlobalValue,
offset: ir::immediates::Offset32,
global_type: ir::Type,
readonly: bool,
isa: &dyn TargetIsa,
) {
// We need to load a pointer from the `base` global value, so insert a new `global_value`
// instruction. This depends on the iterative legalization loop. Note that the IR verifier
// detects any cycles in the `load` globals.
let ptr_ty = isa.pointer_type();
let mut pos = FuncCursor::new(func).at_inst(inst);
pos.use_srcloc(inst);
// Get the value for the base. For tidiness, expand VMContext here so that we avoid
// `vmctx_addr` which creates an otherwise unneeded value alias.
let base_addr = if let ir::GlobalValueData::VMContext = pos.func.global_values[base] {
pos.func
.special_param(ir::ArgumentPurpose::VMContext)
.expect("Missing vmctx parameter")
} else {
pos.ins().global_value(ptr_ty, base)
};
// Global-value loads are always notrap and aligned. They may be readonly.
let mut mflags = ir::MemFlags::trusted();
if readonly {
mflags.set_readonly();
}
// Perform the load.
pos.func
.dfg
.replace(inst)
.load(global_type, mflags, base_addr, offset);
}
/// Expand a `global_value` instruction for a symbolic name global.
fn symbol(
inst: ir::Inst,
func: &mut ir::Function,
gv: ir::GlobalValue,
isa: &dyn TargetIsa,
tls: bool,
) {
let ptr_ty = isa.pointer_type();
if tls {
func.dfg.replace(inst).tls_value(ptr_ty, gv);
} else {
func.dfg.replace(inst).symbol_value(ptr_ty, gv);
}
}
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
fn verify_global_values(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> {
let mut cycle_seen = false;
let mut seen = SparseSet::new();
'gvs: for gv in self.func.global_values.keys() {
seen.clear();
seen.insert(gv);
let mut cur = gv;
loop {
match self.func.global_values[cur] {
ir::GlobalValueData::Load { base, .. }
| ir::GlobalValueData::IAddImm { base, .. } => {
if seen.insert(base).is_some() {
if !cycle_seen {
errors.report((
gv,
format!("global value cycle: {}", DisplayList(seen.as_slice())),
));
// ensures we don't report the cycle multiple times
cycle_seen = true;
}
continue 'gvs;
}
cur = base;
}
_ => break,
}
}
match self.func.global_values[gv] {
ir::GlobalValueData::VMContext { .. } => {
if self
.func
.special_param(ir::ArgumentPurpose::VMContext)
.is_none()
{
errors.report((gv, format!("undeclared vmctx reference {}", gv)));
}
}
ir::GlobalValueData::IAddImm {
base, global_type, ..
} => {
if !global_type.is_int() {
errors.report((
gv,
format!("iadd_imm global value with non-int type {}", global_type),
));
} else if let Some(isa) = self.isa {
let base_type = self.func.global_values[base].global_type(isa);
if global_type != base_type {
errors.report((
gv,
format!(
"iadd_imm type {} differs from operand type {}",
global_type, base_type
),
));
}
}
}
ir::GlobalValueData::Load { base, .. } => {
if let Some(isa) = self.isa {
let base_type = self.func.global_values[base].global_type(isa);
let pointer_type = isa.pointer_type();
if base_type != pointer_type {
errors.report((
gv,
format!(
"base {} has type {}, which is not the pointer type {}",
base, base_type, pointer_type
),
));
}
}
}
_ => {}
}
}
// Invalid global values shouldn't stop us from verifying the rest of the function
Ok(())
}
fn verify_heaps(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> {
if let Some(isa) = self.isa {
for (heap, heap_data) in &self.func.heaps {
let base = heap_data.base;
if !self.func.global_values.is_valid(base) {
return errors.nonfatal((heap, format!("invalid base global value {}", base)));
}
let pointer_type = isa.pointer_type();
let base_type = self.func.global_values[base].global_type(isa);
if base_type != pointer_type {
errors.report((
heap,
format!(
"heap base has type {}, which is not the pointer type {}",
base_type, pointer_type
),
));
}
if let ir::HeapStyle::Dynamic { bound_gv, .. } = heap_data.style {
if !self.func.global_values.is_valid(bound_gv) {
return errors
.nonfatal((heap, format!("invalid bound global value {}", bound_gv)));
}
let bound_type = self.func.global_values[bound_gv].global_type(isa);
if pointer_type != bound_type {
errors.report((
heap,
format!(
"heap pointer type {} differs from the type of its bound, {}",
pointer_type, bound_type
),
));
}
}
}
}
Ok(())
}
fn verify_tables(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> {
if let Some(isa) = self.isa {
for (table, table_data) in &self.func.tables {
let base = table_data.base_gv;
if !self.func.global_values.is_valid(base) {
return errors.nonfatal((table, format!("invalid base global value {}", base)));
}
let pointer_type = isa.pointer_type();
let base_type = self.func.global_values[base].global_type(isa);
if base_type != pointer_type {
errors.report((
table,
format!(
"table base has type {}, which is not the pointer type {}",
base_type, pointer_type
),
));
}
let bound_gv = table_data.bound_gv;
if !self.func.global_values.is_valid(bound_gv) {
return errors
.nonfatal((table, format!("invalid bound global value {}", bound_gv)));
}
let index_type = table_data.index_type;
let bound_type = self.func.global_values[bound_gv].global_type(isa);
if index_type != bound_type {
errors.report((
table,
format!(
"table index type {} differs from the type of its bound, {}",
index_type, bound_type
),
));
}
}
}
Ok(())
}
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 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
pub fn simple_legalize(func: &mut ir::Function, cfg: &mut ControlFlowGraph, isa: &dyn TargetIsa) {
trace!("Pre-legalization function:\n{}", func.display());
let mut pos = FuncCursor::new(func);
let func_begin = pos.position();
pos.set_position(func_begin);
while let Some(_block) = pos.next_block() {
let mut prev_pos = pos.position();
while let Some(inst) = pos.next_inst() {
match pos.func.dfg[inst] {
// control flow
InstructionData::CondTrap {
opcode:
opcode @ (ir::Opcode::Trapnz | ir::Opcode::Trapz | ir::Opcode::ResumableTrapnz),
arg,
code,
} => {
expand_cond_trap(inst, &mut pos.func, cfg, opcode, arg, code);
}
// memory and constants
InstructionData::UnaryGlobalValue {
opcode: ir::Opcode::GlobalValue,
global_value,
} => expand_global_value(inst, &mut pos.func, isa, global_value),
InstructionData::HeapAddr {
opcode: ir::Opcode::HeapAddr,
heap,
arg,
offset,
size,
} => expand_heap_addr(inst, &mut pos.func, cfg, isa, heap, arg, offset, size),
InstructionData::HeapLoad {
opcode: ir::Opcode::HeapLoad,
heap_imm,
arg,
} => expand_heap_load(inst, &mut pos.func, cfg, isa, heap_imm, arg),
InstructionData::HeapStore {
opcode: ir::Opcode::HeapStore,
heap_imm,
args,
} => expand_heap_store(inst, &mut pos.func, cfg, isa, heap_imm, args[0], args[1]),
InstructionData::StackLoad {
opcode: ir::Opcode::StackLoad,
stack_slot,
offset,
} => {
let ty = pos.func.dfg.value_type(pos.func.dfg.first_result(inst));
let addr_ty = isa.pointer_type();
let mut pos = FuncCursor::new(pos.func).at_inst(inst);
pos.use_srcloc(inst);
let addr = pos.ins().stack_addr(addr_ty, stack_slot, offset);
// Stack slots are required to be accessible and aligned.
let mflags = MemFlags::trusted();
pos.func.dfg.replace(inst).load(ty, mflags, addr, 0);
}
InstructionData::StackStore {
opcode: ir::Opcode::StackStore,
arg,
stack_slot,
offset,
} => {
let addr_ty = isa.pointer_type();
let mut pos = FuncCursor::new(pos.func).at_inst(inst);
pos.use_srcloc(inst);
let addr = pos.ins().stack_addr(addr_ty, stack_slot, offset);
let mut mflags = MemFlags::new();
// Stack slots are required to be accessible and aligned.
mflags.set_notrap();
mflags.set_aligned();
pos.func.dfg.replace(inst).store(mflags, arg, addr, 0);
}
InstructionData::DynamicStackLoad {
opcode: ir::Opcode::DynamicStackLoad,
dynamic_stack_slot,
} => {
let ty = pos.func.dfg.value_type(pos.func.dfg.first_result(inst));
assert!(ty.is_dynamic_vector());
let addr_ty = isa.pointer_type();
let mut pos = FuncCursor::new(pos.func).at_inst(inst);
pos.use_srcloc(inst);
let addr = pos.ins().dynamic_stack_addr(addr_ty, dynamic_stack_slot);
// Stack slots are required to be accessible and aligned.
let mflags = MemFlags::trusted();
pos.func.dfg.replace(inst).load(ty, mflags, addr, 0);
}
InstructionData::DynamicStackStore {
opcode: ir::Opcode::DynamicStackStore,
arg,
dynamic_stack_slot,
} => {
pos.use_srcloc(inst);
let addr_ty = isa.pointer_type();
let vector_ty = pos.func.dfg.value_type(arg);
assert!(vector_ty.is_dynamic_vector());
let addr = pos.ins().dynamic_stack_addr(addr_ty, dynamic_stack_slot);
let mut mflags = MemFlags::new();
// Stack slots are required to be accessible and aligned.
mflags.set_notrap();
mflags.set_aligned();
pos.func.dfg.replace(inst).store(mflags, arg, addr, 0);
}
InstructionData::TableAddr {
opcode: ir::Opcode::TableAddr,
table,
arg,
offset,
} => expand_table_addr(isa, inst, &mut pos.func, table, arg, offset),
InstructionData::BinaryImm64 { opcode, arg, imm } => {
let is_signed = match opcode {
ir::Opcode::IaddImm
| ir::Opcode::IrsubImm
| ir::Opcode::ImulImm
| ir::Opcode::SdivImm
| ir::Opcode::SremImm
| ir::Opcode::IfcmpImm => true,
_ => false,
};
let imm = imm_const(&mut pos, arg, imm, is_signed);
let replace = pos.func.dfg.replace(inst);
match opcode {
// bitops
ir::Opcode::BandImm => {
replace.band(arg, imm);
}
ir::Opcode::BorImm => {
replace.bor(arg, imm);
}
ir::Opcode::BxorImm => {
replace.bxor(arg, imm);
}
// bitshifting
ir::Opcode::IshlImm => {
replace.ishl(arg, imm);
}
ir::Opcode::RotlImm => {
replace.rotl(arg, imm);
}
ir::Opcode::RotrImm => {
replace.rotr(arg, imm);
}
ir::Opcode::SshrImm => {
replace.sshr(arg, imm);
}
ir::Opcode::UshrImm => {
replace.ushr(arg, imm);
}
// math
ir::Opcode::IaddImm => {
replace.iadd(arg, imm);
}
ir::Opcode::IrsubImm => {
// note: arg order reversed
replace.isub(imm, arg);
}
ir::Opcode::ImulImm => {
replace.imul(arg, imm);
}
ir::Opcode::SdivImm => {
replace.sdiv(arg, imm);
}
ir::Opcode::SremImm => {
replace.srem(arg, imm);
}
ir::Opcode::UdivImm => {
replace.udiv(arg, imm);
}
ir::Opcode::UremImm => {
replace.urem(arg, imm);
}
// comparisons
ir::Opcode::IfcmpImm => {
replace.ifcmp(arg, imm);
}
_ => prev_pos = pos.position(),
};
}
// comparisons
InstructionData::IntCompareImm {
opcode: ir::Opcode::IcmpImm,
cond,
arg,
imm,
} => {
let imm = imm_const(&mut pos, arg, imm, true);
pos.func.dfg.replace(inst).icmp(cond, arg, imm);
}
_ => {
prev_pos = pos.position();
continue;
}
}
// Legalization implementations require fixpoint loop here.
// TODO: fix this.
pos.set_position(prev_pos);
}
}
trace!("Post-legalization function:\n{}", func.display());
}
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 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
fn bounds_check_and_compute_addr(
pos: &mut FuncCursor,
cfg: &mut ControlFlowGraph,
isa: &dyn TargetIsa,
heap: ir::Heap,
// Dynamic operand indexing into the heap.
index: ir::Value,
// Static immediate added to the index.
offset: u32,
// Static size of the heap access.
access_size: u8,
) -> ir::Value {
let pointer_type = isa.pointer_type();
let spectre = isa.flags().enable_heap_access_spectre_mitigation();
let offset_and_size = offset_plus_size(offset, access_size);
let ir::HeapData {
base: _,
min_size,
offset_guard_size: guard_size,
style,
index_type,
} = pos.func.heaps[heap].clone();
let index = cast_index_to_pointer_ty(index, index_type, pointer_type, pos);
// We need to emit code that will trap (or compute an address that will trap
// when accessed) if
//
// index + offset + access_size > bound
//
// or if the `index + offset + access_size` addition overflows.
//
// Note that we ultimately want a 64-bit integer (we only target 64-bit
// architectures at the moment) and that `offset` is a `u32` and
// `access_size` is a `u8`. This means that we can add the latter together
// as `u64`s without fear of overflow, and we only have to be concerned with
// whether adding in `index` will overflow.
//
// Finally, the following right-hand sides of the matches do have a little
// bit of duplicated code across them, but I think writing it this way is
// worth it for readability and seeing very clearly each of our cases for
// different bounds checks and optimizations of those bounds checks. It is
// intentionally written in a straightforward case-matching style that will
// hopefully make it easy to port to ISLE one day.
match style {
// ====== Dynamic Memories ======
//
// 1. First special case for when `offset + access_size == 1`:
//
// index + 1 > bound
// ==> index >= bound
//
// 1.a. When Spectre mitigations are enabled, avoid duplicating
// bounds checks between the mitigations and the regular bounds
// checks.
ir::HeapStyle::Dynamic { bound_gv } if offset_and_size == 1 && spectre => {
let bound = pos.ins().global_value(pointer_type, bound_gv);
compute_addr(
isa,
pos,
heap,
pointer_type,
index,
offset,
Some(SpectreOobComparison {
cc: IntCC::UnsignedGreaterThanOrEqual,
lhs: index,
rhs: bound,
}),
)
}
// 1.b. Emit explicit `index >= bound` bounds checks.
ir::HeapStyle::Dynamic { bound_gv } if offset_and_size == 1 => {
let bound = pos.ins().global_value(pointer_type, bound_gv);
let oob = pos
.ins()
.icmp(IntCC::UnsignedGreaterThanOrEqual, index, bound);
pos.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds);
compute_addr(isa, pos, heap, pointer_type, index, offset, None)
}
// 2. Second special case for when `offset + access_size <= min_size`.
//
// We know that `bound >= min_size`, so we can do the following
// comparison, without fear of the right-hand side wrapping around:
//
// index + offset + access_size > bound
// ==> index > bound - (offset + access_size)
//
// 2.a. Dedupe bounds checks with Spectre mitigations.
ir::HeapStyle::Dynamic { bound_gv } if offset_and_size <= min_size.into() && spectre => {
let bound = pos.ins().global_value(pointer_type, bound_gv);
let adjusted_bound = pos.ins().iadd_imm(bound, -(offset_and_size as i64));
compute_addr(
isa,
pos,
heap,
pointer_type,
index,
offset,
Some(SpectreOobComparison {
cc: IntCC::UnsignedGreaterThan,
lhs: index,
rhs: adjusted_bound,
}),
)
}
// 2.b. Emit explicit `index > bound - (offset + access_size)` bounds
// checks.
ir::HeapStyle::Dynamic { bound_gv } if offset_and_size <= min_size.into() => {
let bound = pos.ins().global_value(pointer_type, bound_gv);
let adjusted_bound = pos.ins().iadd_imm(bound, -(offset_and_size as i64));
let oob = pos
.ins()
.icmp(IntCC::UnsignedGreaterThan, index, adjusted_bound);
pos.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds);
compute_addr(isa, pos, heap, pointer_type, index, offset, None)
}
// 3. General case for dynamic memories:
//
// index + offset + access_size > bound
//
// And we have to handle the overflow case in the left-hand side.
//
// 3.a. Dedupe bounds checks with Spectre mitigations.
ir::HeapStyle::Dynamic { bound_gv } if spectre => {
let access_size_val = pos.ins().iconst(pointer_type, offset_and_size as i64);
let adjusted_index =
pos.ins()
.uadd_overflow_trap(index, access_size_val, ir::TrapCode::HeapOutOfBounds);
let bound = pos.ins().global_value(pointer_type, bound_gv);
compute_addr(
isa,
pos,
heap,
pointer_type,
index,
offset,
Some(SpectreOobComparison {
cc: IntCC::UnsignedGreaterThan,
lhs: adjusted_index,
rhs: bound,
}),
)
}
// 3.b. Emit an explicit `index + offset + access_size > bound`
// check.
ir::HeapStyle::Dynamic { bound_gv } => {
let access_size_val = pos.ins().iconst(pointer_type, offset_and_size as i64);
let adjusted_index =
pos.ins()
.uadd_overflow_trap(index, access_size_val, ir::TrapCode::HeapOutOfBounds);
let bound = pos.ins().global_value(pointer_type, bound_gv);
let oob = pos
.ins()
.icmp(IntCC::UnsignedGreaterThan, adjusted_index, bound);
pos.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds);
compute_addr(isa, pos, heap, pointer_type, index, offset, None)
}
// ====== Static Memories ======
//
// With static memories we know the size of the heap bound at compile
// time.
//
// 1. First special case: trap immediately if `offset + access_size >
// bound`, since we will end up being out-of-bounds regardless of the
// given `index`.
ir::HeapStyle::Static { bound } if offset_and_size > bound.into() => {
pos.ins().trap(ir::TrapCode::HeapOutOfBounds);
// Split the block, as the trap is a terminator instruction.
let curr_block = pos.current_block().expect("Cursor is not in a block");
let new_block = pos.func.dfg.make_block();
pos.insert_block(new_block);
cfg.recompute_block(pos.func, curr_block);
cfg.recompute_block(pos.func, new_block);
let null = pos.ins().iconst(pointer_type, 0);
return null;
}
// 2. Second special case for when we can completely omit explicit
// bounds checks for 32-bit static memories.
//
// First, let's rewrite our comparison to move all of the constants
// to one side:
//
// index + offset + access_size > bound
// ==> index > bound - (offset + access_size)
//
// We know the subtraction on the right-hand side won't wrap because
// we didn't hit the first special case.
//
// Additionally, we add our guard pages (if any) to the right-hand
// side, since we can rely on the virtual memory subsystem at runtime
// to catch out-of-bound accesses within the range `bound .. bound +
// guard_size`. So now we are dealing with
//
// index > bound + guard_size - (offset + access_size)
//
// Note that `bound + guard_size` cannot overflow for
// correctly-configured heaps, as otherwise the heap wouldn't fit in
// a 64-bit memory space.
//
// The complement of our should-this-trap comparison expression is
// the should-this-not-trap comparison expression:
//
// index <= bound + guard_size - (offset + access_size)
//
// If we know the right-hand side is greater than or equal to
// `u32::MAX`, then
//
// index <= u32::MAX <= bound + guard_size - (offset + access_size)
//
// This expression is always true when the heap is indexed with
// 32-bit integers because `index` cannot be larger than
// `u32::MAX`. This means that `index` is always either in bounds or
// within the guard page region, neither of which require emitting an
// explicit bounds check.
ir::HeapStyle::Static { bound }
if index_type == ir::types::I32
&& u64::from(u32::MAX)
<= u64::from(bound) + u64::from(guard_size) - offset_and_size =>
{
compute_addr(isa, pos, heap, pointer_type, index, offset, None)
}
// 3. General case for static memories.
//
// We have to explicitly test whether
//
// index > bound - (offset + access_size)
//
// and trap if so.
//
// Since we have to emit explicit bounds checks, we might as well be
// precise, not rely on the virtual memory subsystem at all, and not
// factor in the guard pages here.
//
// 3.a. Dedupe the Spectre mitigation and the explicit bounds check.
ir::HeapStyle::Static { bound } if spectre => {
// NB: this subtraction cannot wrap because we didn't hit the first
// special case.
let adjusted_bound = u64::from(bound) - offset_and_size;
let adjusted_bound = pos.ins().iconst(pointer_type, adjusted_bound as i64);
compute_addr(
isa,
pos,
heap,
pointer_type,
index,
offset,
Some(SpectreOobComparison {
cc: IntCC::UnsignedGreaterThan,
lhs: index,
rhs: adjusted_bound,
}),
)
}
// 3.b. Emit the explicit `index > bound - (offset + access_size)`
// check.
ir::HeapStyle::Static { bound } => {
// See comment in 3.a. above.
let adjusted_bound = u64::from(bound) - offset_and_size;
let oob = pos
.ins()
.icmp_imm(IntCC::UnsignedGreaterThan, index, adjusted_bound as i64);
pos.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds);
compute_addr(isa, pos, heap, pointer_type, index, offset, None)
}
}
}
fn cast_index_to_pointer_ty(
index: ir::Value,
index_ty: ir::Type,
pointer_ty: ir::Type,
pos: &mut FuncCursor,
) -> ir::Value {
if index_ty == pointer_ty {
return index;
}
// Note that using 64-bit heaps on a 32-bit host is not currently supported,
// would require at least a bounds check here to ensure that the truncation
// from 64-to-32 bits doesn't lose any upper bits. For now though we're
// mostly interested in the 32-bit-heaps-on-64-bit-hosts cast.
assert!(index_ty.bits() < pointer_ty.bits());
// Convert `index` to `addr_ty`.
let extended_index = pos.ins().uextend(pointer_ty, index);
// Add debug value-label alias so that debuginfo can name the extended
// value as the address
let loc = pos.srcloc();
let loc = RelSourceLoc::from_base_offset(pos.func.params.base_srcloc(), loc);
pos.func
.stencil
.dfg
.add_value_label_alias(extended_index, loc, index);
extended_index
}
struct SpectreOobComparison {
cc: IntCC,
lhs: ir::Value,
rhs: ir::Value,
}
/// Emit code for the base address computation of a `heap_addr` instruction,
/// without any bounds checks (other than optional Spectre mitigations).
fn compute_addr(
isa: &dyn TargetIsa,
pos: &mut FuncCursor,
heap: ir::Heap,
addr_ty: ir::Type,
index: ir::Value,
offset: u32,
// If we are performing Spectre mitigation with conditional selects, the
// values to compare and the condition code that indicates an out-of bounds
// condition; on this condition, the conditional move will choose a
// speculatively safe address (a zero / null pointer) instead.
spectre_oob_comparison: Option<SpectreOobComparison>,
) -> ir::Value {
debug_assert_eq!(pos.func.dfg.value_type(index), addr_ty);
// Add the heap base address base
let base = if isa.flags().enable_pinned_reg() && isa.flags().use_pinned_reg_as_heap_base() {
let base = pos.ins().get_pinned_reg(isa.pointer_type());
trace!(" inserting: {}", pos.func.dfg.display_value_inst(base));
base
} else {
let base_gv = pos.func.heaps[heap].base;
let base = pos.ins().global_value(addr_ty, base_gv);
trace!(" inserting: {}", pos.func.dfg.display_value_inst(base));
base
};
if let Some(SpectreOobComparison { cc, lhs, rhs }) = spectre_oob_comparison {
let final_base = pos.ins().iadd(base, index);
// NB: The addition of the offset immediate must happen *before* the
// `select_spectre_guard`. If it happens after, then we potentially are
// letting speculative execution read the whole first 4GiB of memory.
let final_addr = if offset == 0 {
final_base
} else {
let final_addr = pos.ins().iadd_imm(final_base, offset as i64);
trace!(
" inserting: {}",
pos.func.dfg.display_value_inst(final_addr)
);
final_addr
};
let zero = pos.ins().iconst(addr_ty, 0);
trace!(" inserting: {}", pos.func.dfg.display_value_inst(zero));
let cmp = pos.ins().icmp(cc, lhs, rhs);
trace!(" inserting: {}", pos.func.dfg.display_value_inst(cmp));
let value = pos.ins().select_spectre_guard(cmp, zero, final_addr);
trace!(" inserting: {}", pos.func.dfg.display_value_inst(value));
value
} else if offset == 0 {
let addr = pos.ins().iadd(base, index);
trace!(" inserting: {}", pos.func.dfg.display_value_inst(addr));
addr
} else {
let final_base = pos.ins().iadd(base, index);
trace!(
" inserting: {}",
pos.func.dfg.display_value_inst(final_base)
);
let addr = pos.ins().iadd_imm(final_base, offset as i64);
trace!(" inserting: {}", pos.func.dfg.display_value_inst(addr));
addr
}
}
sourcepub fn pointer_bits(&self) -> u8
pub fn pointer_bits(&self) -> u8
Get the width of pointers on this ISA, in units of bits.
sourcepub fn pointer_bytes(&self) -> u8
pub fn pointer_bytes(&self) -> u8
Get the width of pointers on this ISA, in units of bytes.
Examples found in repository?
913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933
pub fn do_preopt(func: &mut Function, cfg: &mut ControlFlowGraph, isa: &dyn TargetIsa) {
let _tt = timing::preopt();
let mut pos = FuncCursor::new(func);
let native_word_width = isa.pointer_bytes() as u32;
let mut optimizer = simplify::peephole_optimizer(isa);
while let Some(block) = pos.next_block() {
while let Some(inst) = pos.next_inst() {
simplify::apply_all(&mut optimizer, &mut pos, inst, native_word_width);
// Try to transform divide-by-constant into simpler operations.
if let Some(divrem_info) = get_div_info(inst, &pos.func.dfg) {
do_divrem_transformation(&divrem_info, &mut pos, inst);
continue;
}
branch_order(&mut pos, cfg, block, inst);
}
}
}
sourcepub fn frontend_config(&self) -> TargetFrontendConfig
pub fn frontend_config(&self) -> TargetFrontendConfig
Get the information needed by frontends producing Cranelift IR.