// Branch-hinting and compilation-hints attributes.
//
// `#[likely]`, `#[freq = 16]`, `#[targets(f: 0.73)]` are advisory
// `metadata.code.*` metadata. The lexer has no dedicated tokens for them -- they
// are ordinary attributes -- so which hint an attribute stands for is recovered
// here, and anything else in a hint position is rejected.
///|
/// What a hint attribute asks for, before it has been checked against the
/// instruction it prefixes.
enum HintSetter {
Branch(Bool)
Freq(Int)
Targets(Array[(Ident, Int)])
} derive(Debug)
///|
/// `metadata.code.instr_freq`: never optimize.
let never_opt : Int = 0
///|
/// `metadata.code.instr_freq`: always optimize.
let always_opt : Int = 127
///|
/// Encode an executions-per-call ratio as the section's offset base-2
/// logarithm, so 1.0 (once per call) becomes 32.
///
/// Clamped to [1, 64]: 0 and 127 are the reserved never/always values, which
/// have their own attributes.
fn freq_of_ratio(r : Double) -> Int {
if r <= 0.0 {
1
} else {
let l = @math.log2(r).floor().to_int() + 32
if l < 1 {
1
} else if l > 64 {
64
} else {
l
}
}
}
///|
/// A call-target frequency is written as a fraction of 1 and stored as a whole
/// percent, which is what the section holds.
fn target_percent(p : (Position, Position), n : String) -> Int {
let f = @string.parse_double(n) catch {
_ =>
fail_at(
loc_of(p),
"A call-target frequency must be between 0 and 1.",
default=0.0,
)
}
if f < 0.0 || f > 1.0 {
record_error(loc_of(p), "A call-target frequency must be between 0 and 1.")
}
(f * 100.0).round().to_int()
}
///|
/// Recover which hint an attribute stands for.
fn hint_of_attr(p : (Position, Position), a : @ast.Attribute) -> HintSetter {
fn bad() -> HintSetter {
fail_at(
loc_of(p),
"Expected a hint attribute: '#[likely]', '#[unlikely]', '#[freq = n]', '#[never_opt]' or '#[always_opt]'.",
default=Branch(true),
)
}
match (a.attr_name, a.attr_value) {
("likely", None) => Branch(true)
("unlikely", None) => Branch(false)
("never_opt", None) => Freq(never_opt)
("always_opt", None) => Freq(always_opt)
("freq", Some(v)) =>
match v.desc {
Int(n) | Float(n) =>
Freq(
freq_of_ratio(@string.parse_double(n) catch { _ => return bad() }),
)
_ => bad()
}
_ => bad()
}
}
///|
/// `#[targets(f: 0.73, ...)]` takes a LIST, which neither attribute shape can
/// carry, so it has its own production. The name is an ordinary IDENT -- the
/// `(` after it is what selects that production -- so it is checked here rather
/// than by the lexer.
fn targets_of_attr(
p : (Position, Position),
name : String,
l : Array[(Ident, Int)],
) -> HintSetter {
if name != "targets" {
record_error(
loc_of(p),
"Expected a hint attribute: the only one taking a list is '#[targets(f: 0.73, ...)]'.",
)
}
Targets(l)
}
///|
/// Whether a branch hint may prefix this instruction.
fn is_branch_hint_target(d : InstrDesc[Location]) -> Bool {
match d {
If(..)
| BrIf(_)
| BrOnNull(_)
| BrOnNonNull(_)
| BrOnCast(_)
| BrOnCastFail(_)
| BrOnCastDescEq(_)
| BrOnCastDescEqFail(_) => true
_ => false
}
}
///|
/// Whether a frequency hint may prefix this instruction.
///
/// A frequency guides inlining, loop unrolling and block deferral, so it is
/// meaningful on a call or a control instruction.
fn is_freq_target(d : InstrDesc[Location]) -> Bool {
match d {
Call(_)
| TailCall(_)
| Block(..)
| Loop(..)
| While(..)
| If(..)
| TryTable(..)
| Try(..)
| TryCatch(..)
| Dispatch(..)
| Match(..) => true
_ => is_branch_hint_target(d)
}
}
///|
/// Call targets need a call. Whether that call is INDIRECT -- the only kind the
/// hint means anything for -- depends on what the callee resolves to, which the
/// parser cannot know; the typer decides.
fn is_targets_target(d : InstrDesc[Location]) -> Bool {
d is Call(_) || d is TailCall(_)
}
///|
/// Attach hints to the instruction a hint attribute prefixes.
///
/// A hint attribute takes the WHOLE expression that follows it, so the
/// instruction it lands on may be anything an expression can be. The target
/// checks below are what hold the placement rule: `#[freq = 4] f(x) + 1` lands
/// on the BinOp and is rejected.
///
/// The advice to parenthesize is only offered when the hint landed on an
/// operator form, where a tighter sub-expression exists that it could have been
/// meant for. On a block or a branch the placement is simply wrong and
/// parentheses would not change what the hint lands on.
fn hinted(
aloc : (Position, Position),
setters : Array[HintSetter],
i : Instr[Location],
) -> Instr[Location] {
let span = loc_of(aloc)
let hint : String? = match i.desc {
BinOpI(_)
| UnOpI(_)
| Cast(_)
| CastDesc(_)
| Test(_)
| On(_)
| NonNull(_)
| StructGet(_)
| ArrayGet(_)
| GetDescriptor(_)
| Select(_) =>
Some(
"A hint takes the whole expression that follows it. Parenthesize the part you meant.",
)
_ => None
}
let mut hints = i.hints
for s in setters {
match s {
Branch(b) => {
if !is_branch_hint_target(i.desc) {
fail_at(
span,
"A branch hint may only prefix a conditional branch (if, br_if, or br_on_*).",
default=(),
hint~,
)
}
hints = { ..hints, branch: Some({ value: b, loc: span }) }
}
Freq(f) => {
if !is_freq_target(i.desc) {
fail_at(
span,
"A frequency hint may only prefix a call or a control instruction.",
default=(),
hint~,
)
}
hints = { ..hints, freq: Some({ value: f, loc: span }) }
}
Targets(l) => {
if !is_targets_target(i.desc) {
fail_at(
span,
"A call-target hint may only prefix a call.",
default=(),
hint~,
)
}
hints = { ..hints, targets: Some({ value: l, loc: span }) }
}
}
}
{ ..i, hints, }
}