Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cranelift/codegen/src/binemit/memorysink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ impl<'a> CodeSink for MemoryCodeSink<'a> {

/// A `RelocSink` implementation that does nothing, which is convenient when
/// compiling code that does not relocate anything.
#[derive(Default)]
pub struct NullRelocSink {}

impl RelocSink for NullRelocSink {
Expand All @@ -219,6 +220,7 @@ impl RelocSink for NullRelocSink {

/// A `TrapSink` implementation that does nothing, which is convenient when
/// compiling code that does not rely on trapping semantics.
#[derive(Default)]
pub struct NullTrapSink {}

impl TrapSink for NullTrapSink {
Expand Down
71 changes: 62 additions & 9 deletions cranelift/codegen/src/binemit/stackmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,68 @@ use alloc::vec::Vec;
type Num = u32;
const NUM_BITS: usize = core::mem::size_of::<Num>() * 8;

/// A stack map is a bitmap with one bit per machine word on the stack. Stack
/// maps are created at `safepoint` instructions and record all live reference
/// values that are on the stack. All slot kinds, except `OutgoingArg` are
/// captured in a stack map. The `OutgoingArg`'s will be captured in the callee
/// function as `IncomingArg`'s.
/// Stack maps record which words in a stack frame contain live GC references at
/// a given instruction pointer.
///
/// The first value in the bitmap is of the lowest addressed slot on the stack.
/// As all stacks in Isa's supported by Cranelift grow down, this means that
/// first value is of the top of the stack and values proceed down the stack.
/// Logically, a set of stack maps for a function record a table of the form:
///
/// ```text
/// +---------------------+-------------------------------------------+
/// | Instruction Pointer | SP-Relative Offsets of Live GC References |
/// +---------------------+-------------------------------------------+
/// | 0x12345678 | 2, 6, 12 |
/// | 0x1234abcd | 2, 6 |
/// | ... | ... |
/// +---------------------+-------------------------------------------+
/// ```
///
/// Where "instruction pointer" is an instruction pointer within the function,
/// and "offsets of live GC references" contains the offsets (in units of words)
/// from the frame's stack pointer where live GC references are stored on the
/// stack. Instruction pointers within the function that do not have an entry in
/// this table are not GC safepoints.
///
/// Because
///
/// * offsets of live GC references are relative from the stack pointer, and
/// * stack frames grow down from higher addresses to lower addresses,
///
/// to get a pointer to a live reference at offset `x` within a stack frame, you
/// add `x` from the frame's stack pointer.
///
/// For example, to calculate the pointer to the live GC reference inside "frame
/// 1" below, you would do `frame_1_sp + x`:
///
/// ```text
/// Stack
/// +-------------------+
/// | Frame 0 |
/// | |
/// | | |
/// | +-------------------+ <--- Frame 0's SP
/// | | Frame 1 |
/// Grows | |
/// down | |
/// | | Live GC reference | --+--
/// | | | |
/// | | | |
/// V | | x = offset of live GC reference
/// | | |
/// | | |
/// +-------------------+ --+-- <--- Frame 1's SP
/// | Frame 2 |
/// | ... |
/// ```
///
/// An individual `Stackmap` is associated with just one instruction pointer
/// within the function, contains the size of the stack frame, and represents
/// the stack frame as a bitmap. There is one bit per word in the stack frame,
/// and if the bit is set, then the word contains a live GC reference.
///
/// Note that a caller's `OutgoingArg` stack slots and callee's `IncomingArg`
/// stack slots overlap, so we must choose which function's stack maps record
/// live GC references in these slots. We record the `IncomingArg`s in the
/// callee's stack map.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Stackmap {
Expand Down Expand Up @@ -50,7 +103,7 @@ impl Stackmap {

// Refer to the doc comment for `Stackmap` above to understand the
// bitmap representation used here.
let map_size = (info.frame_size + info.inbound_args_size) as usize;
let map_size = (dbg!(info.frame_size) + dbg!(info.inbound_args_size)) as usize;
let word_size = isa.pointer_bytes() as usize;
let num_words = map_size / word_size;

Expand Down
103 changes: 103 additions & 0 deletions cranelift/filetests/filetests/stackmaps/call.clif
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
test stackmaps
set enable_safepoints=true
target x86_64

function %icall_fast(r64) -> r64 fast {
; check: function %icall_fast
; nextln: ss0 = spill_slot 8, offset -32
fn0 = %none()
block0(v0: r64):
; check: ss0] v0 = spill v2
; check: safepoint v0
call fn0()
return v0
}
; check: Stack maps:
; nextln:
; nextln: safepoint v0
; nextln: - mapped words: 4
; nextln: - live: [0]

function %icall_sys_v(r64) -> r64 system_v {
; check: function %icall_sys_v
; nextln: ss0 = spill_slot 8, offset -32
fn0 = %none()
block0(v0: r64):
; check: ss0] v0 = spill v2
; check: safepoint v0
call fn0()
return v0
}
; check: Stack maps:
; nextln:
; nextln: safepoint v0
; nextln: - mapped words: 4
; nextln: - live: [0]

function %icall_fastcall(r64) -> r64 windows_fastcall {
; check: function %icall_fastcall
; nextln: ss0 = spill_slot 8, offset -32
; nextln: ss1 = incoming_arg 24, offset -24
; nextln: ss2 = explicit_slot 32, offset -64
fn0 = %none()
block0(v0: r64):
; check: ss0] v0 = spill v2
; check: safepoint v0
call fn0()
return v0
}
; check: Stack maps:
; nextln:
; nextln: safepoint v0
; nextln: - mapped words: 8
; nextln: - live: [4]

function %call_fast(r64) -> r64 fast {
; check: function %call_fast
; nextln: ss0 = spill_slot 8, offset -32
fn0 = colocated %none()
block0(v0: r64):
; check: ss0] v0 = spill v1
; check: safepoint v0
call fn0()
return v0
}
; check: Stack maps:
; nextln:
; nextln: safepoint v0
; nextln: - mapped words: 4
; nextln: - live: [0]

function %call_sys_v(r64) -> r64 system_v {
; check: function %call_sys_v
; nextln: ss0 = spill_slot 8, offset -32
fn0 = colocated %none()
block0(v0: r64):
; check: ss0] v0 = spill v1
; check: safepoint v0
call fn0()
return v0
}
; check: Stack maps:
; nextln:
; nextln: safepoint v0
; nextln: - mapped words: 4
; nextln: - live: [0]

function %call_fastcall(r64) -> r64 windows_fastcall {
; check: function %call_fastcall
; nextln: ss0 = spill_slot 8, offset -32
; nextln: ss1 = incoming_arg 24, offset -24
; nextln: ss2 = explicit_slot 32, offset -64
fn0 = colocated %none()
block0(v0: r64):
; check: ss0] v0 = spill v1
; check: safepoint v0
call fn0()
return v0
}
; check: Stack maps:
; nextln:
; nextln: safepoint v0
; nextln: - mapped words: 8
; nextln: - live: [4]
31 changes: 31 additions & 0 deletions cranelift/filetests/filetests/stackmaps/incoming_args.clif
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
test stackmaps
set enable_safepoints=true
target x86_64

;; Incoming args get included in stack maps.

function %incoming_args(r64, r64, r64, r64, r64) -> r64 windows_fastcall {
; check: r64 [32]
; nextln: ss0 = incoming_arg 8, offset 32
; nextln: ss1 = incoming_arg 24, offset -24
; nextln: ss2 = explicit_slot 32, offset -64

fn0 = %none()
; nextln: sig0 = () fast
; nextln: fn0 = %none sig0

block0(v0: r64, v1: r64, v2: r64, v3: r64, v4: r64):
; check: v4: r64 [ss0]

call fn0()
; check: safepoint v4
; nextln: call_indirect
return v4
}

; check: Stack maps:
; nextln:
; nextln: safepoint v4
; nextln: - mapped words: 13
; nextln: - live: [12]

2 changes: 2 additions & 0 deletions cranelift/filetests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ mod test_safepoint;
mod test_shrink;
mod test_simple_gvn;
mod test_simple_preopt;
mod test_stackmaps;
mod test_unwind;
mod test_verifier;

Expand Down Expand Up @@ -139,6 +140,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::SubtestResult<Box<dyn subtest::
"shrink" => test_shrink::subtest(parsed),
"simple-gvn" => test_simple_gvn::subtest(parsed),
"simple_preopt" => test_simple_preopt::subtest(parsed),
"stackmaps" => test_stackmaps::subtest(parsed),
"unwind" => test_unwind::subtest(parsed),
"verifier" => test_verifier::subtest(parsed),
_ => Err(format!("unknown test command '{}'", parsed.command)),
Expand Down
112 changes: 112 additions & 0 deletions cranelift/filetests/src/test_stackmaps.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult};
use cranelift_codegen::binemit::{self, Addend, CodeOffset, CodeSink, Reloc, Stackmap};
use cranelift_codegen::ir::*;
use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_reader::TestCommand;
use std::borrow::Cow;
use std::fmt::Write;

struct TestStackmaps;

pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> {
assert_eq!(parsed.command, "stackmaps");
if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed))
} else {
Ok(Box::new(TestStackmaps))
}
}

impl SubTest for TestStackmaps {
fn name(&self) -> &'static str {
"stackmaps"
}

fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> {
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());

comp_ctx
.compile(context.isa.expect("`test stackmaps` requires an isa"))
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?;

let mut sink = TestStackMapsSink::default();
binemit::emit_function(
&comp_ctx.func,
|func, inst, div, sink, isa| {
if func.dfg[inst].opcode() == Opcode::Safepoint {
writeln!(&mut sink.text, "{}", func.dfg.display_inst(inst, isa)).unwrap();
}
isa.emit_inst(func, inst, div, sink)
},
&mut sink,
context.isa.expect("`test stackmaps` requires an isa"),
);

let mut text = comp_ctx.func.display(context.isa).to_string();
text.push('\n');
text.push_str("Stack maps:\n");
text.push('\n');
text.push_str(&sink.text);

run_filecheck(&text, context)
}
}

#[derive(Default)]
struct TestStackMapsSink {
offset: u32,
text: String,
}

impl CodeSink for TestStackMapsSink {
fn offset(&self) -> CodeOffset {
self.offset
}

fn put1(&mut self, _: u8) {
self.offset += 1;
}

fn put2(&mut self, _: u16) {
self.offset += 2;
}

fn put4(&mut self, _: u32) {
self.offset += 4;
}

fn put8(&mut self, _: u64) {
self.offset += 8;
}

fn reloc_block(&mut self, _: Reloc, _: CodeOffset) {}
fn reloc_external(&mut self, _: SourceLoc, _: Reloc, _: &ExternalName, _: Addend) {}
fn reloc_constant(&mut self, _: Reloc, _: ConstantOffset) {}
fn reloc_jt(&mut self, _: Reloc, _: JumpTable) {}
fn trap(&mut self, _: TrapCode, _: SourceLoc) {}
fn begin_jumptables(&mut self) {}
fn begin_rodata(&mut self) {}
fn end_codegen(&mut self) {}

fn add_stackmap(&mut self, val_list: &[Value], func: &Function, isa: &dyn TargetIsa) {
let map = Stackmap::from_values(&val_list, func, isa);

writeln!(&mut self.text, " - mapped words: {}", map.mapped_words()).unwrap();
write!(&mut self.text, " - live: [").unwrap();

let mut needs_comma_space = false;
for i in 0..(map.mapped_words() as usize) {
if map.get_bit(i) {
if needs_comma_space {
write!(&mut self.text, ", ").unwrap();
}
needs_comma_space = true;

write!(&mut self.text, "{}", i).unwrap();
}
}

writeln!(&mut self.text, "]").unwrap();
}
}