mirror of
https://github.com/imjasonh/nescript
synced 2026-07-09 17:28:00 +00:00
W0110 inline fallback warning + docs refresh
W0110: when a function marked `inline` has a body shape the IR
lowerer can't splice (conditional early return, loops, nested
control flow, empty void body), the analyzer now emits a
warning at the declaration site so the declined hint is
visible instead of silently falling back to a regular JSR.
Implementation:
- New `W0110` error code in `src/errors/diagnostic.rs` (warning level).
- New `pub fn can_inline_fun(return_type, body) -> bool` in
`src/ir/lowering.rs`, extracted from the existing capture
logic so the analyzer and the IR lowerer share the same
eligibility rules and can never drift.
- New `check_inline_declinability` analyzer pass called from
the tail of `analyze_program`, mirroring the existing
`check_sprite_scanline_budget` / `check_unreachable_states`
passes. Emits W0110 with help + note text pointing at the
two accepted body shapes.
- `capture_inline_bodies` now defers to `can_inline_fun`
instead of duplicating the match pattern, so the two sides
stay in lockstep by construction.
Four regression tests in `src/analyzer/tests.rs` cover the
conditional-return and while-loop declines plus the two
accepted shapes (single-return expression, void sequence).
Example source cleanups: `wrap52` in `examples/war/deck.ne`
and `abs_diff` in both `examples/arrays_and_functions.ne` and
`examples/loop_break_continue.ne` drop the `inline` keyword.
All three were dead hints — the `inline` was being silently
declined before this change, so removing it is source-only;
the three ROMs are byte-identical, all 32 emulator goldens
still match.
Docs refresh
- `docs/language-guide.md`: rewrote the Inline Functions section
(real behaviour + W0110), added W0105/W0106/W0107/W0108/W0109/
W0110 to the warnings table, added the `debug.sprite_overflow*`
builtins + sprite-per-scanline mitigations section to the
Debug Mode docs, added a `cycle_sprites` statement entry and
cross-referenced it from `draw`.
- `docs/nes-reference.md`: fleshed out the "NEScript Memory
Usage" block with the full ZP + high-RAM layout, including
the new `$07EF` / `$07FC` / `$07FD` slots for sprite cycling
and the debug sprite-overflow telemetry.
- `docs/future-work.md`: documented all four debug query
builtins in the "What ships today" block; updated the open
"OAM allocation strategy" question to reference the shipped
`cycle_sprites` path and ask about an automatic-flicker
game attribute as a follow-up.
- `docs/architecture.md`: updated the `ir/` and `optimizer/`
module summaries to describe real inline splicing (now
in lowering, not the optimizer).
- `README.md`: reframed the `inline` bullet from "hint" to
"real splicing for single-return / void-body shapes";
expanded the debug-support bullet to mention the four
query builtins and their stripping in release builds; added
a new bullet for the three-layer sprite-per-scanline
mitigations; bumped the test count from 497 → 694; updated
the war.ne entry to mention the seven compiler bugs are all
fixed and point readers at `git log` (instead of the
deleted COMPILER_BUGS.md).
- `examples/README.md`: same `git log`-pointing rewrite for
the war.ne entry.
Deletions
- `examples/war/COMPILER_BUGS.md` is removed. All seven
catalogued bugs are fixed; the file's historical value
lives in `git log` now. Every source-code comment and doc
reference to the file has been updated to either point at
`git log` or just describe the bug in place.
Test count: 616 unit + 75 integration + 3 doctests = 694 total.
Clippy / fmt clean. 32/32 emulator goldens match.
https://claude.ai/code/session_0143dTgh3UeRrtfHgQwzcv5z
This commit is contained in:
parent
5e5bed39a5
commit
548787ac8a
18 changed files with 487 additions and 858 deletions
|
|
@ -688,6 +688,15 @@ impl Analyzer {
|
|||
// coordinates are skipped because the static analysis
|
||||
// can't know where the sprite will land at runtime.
|
||||
self.check_sprite_scanline_budget(program);
|
||||
|
||||
// Check every `inline fun` declaration against the
|
||||
// IR lowerer's inline-eligibility rules. Functions
|
||||
// whose body shape isn't splicable compile as regular
|
||||
// out-of-line calls — which is correct, but silent:
|
||||
// users who mark a helper `inline` to avoid call
|
||||
// overhead might not realize the hint was declined.
|
||||
// W0110 makes the fallback visible.
|
||||
self.check_inline_declinability(program);
|
||||
}
|
||||
|
||||
/// Qualify `name` under the current scope prefix. If no prefix
|
||||
|
|
@ -1148,6 +1157,47 @@ impl Analyzer {
|
|||
}
|
||||
}
|
||||
|
||||
/// Walk every `inline fun` declaration and check whether the
|
||||
/// IR lowerer will actually inline its body. Functions that
|
||||
/// won't inline (conditional returns, loops, transitions,
|
||||
/// empty void bodies, etc.) compile as regular out-of-line
|
||||
/// calls — correct but silent. W0110 surfaces the fallback
|
||||
/// at the declaration site with help text pointing at the
|
||||
/// two body shapes the inliner does accept.
|
||||
///
|
||||
/// Defers to [`crate::ir::lowering::can_inline_fun`] so the
|
||||
/// two sides (warning + actual capture) can never drift.
|
||||
fn check_inline_declinability(&mut self, program: &Program) {
|
||||
for fun in &program.functions {
|
||||
if !fun.is_inline {
|
||||
continue;
|
||||
}
|
||||
if crate::ir::can_inline_fun(fun.return_type.as_ref(), &fun.body) {
|
||||
continue;
|
||||
}
|
||||
self.diagnostics.push(
|
||||
Diagnostic::warning(
|
||||
ErrorCode::W0110,
|
||||
format!(
|
||||
"`inline fun {}` cannot be inlined; falling back to a regular call",
|
||||
fun.name
|
||||
),
|
||||
fun.span,
|
||||
)
|
||||
.with_help(
|
||||
"the inliner accepts two body shapes: a single `return <expr>` (for \
|
||||
functions with a return type) or a sequence of plain statements with no \
|
||||
control flow (for void functions). Rewrite the body to fit one of those, \
|
||||
or remove the `inline` keyword if the JSR overhead is acceptable",
|
||||
)
|
||||
.with_note(
|
||||
"rejected body shapes include conditional early returns, if/while/for/loop \
|
||||
blocks, transitions, breaks, continues, and nested function definitions",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn register_const(&mut self, c: &ConstDecl) {
|
||||
if self.symbols.contains_key(&c.name) {
|
||||
self.diagnostics.push(Diagnostic::error(
|
||||
|
|
|
|||
|
|
@ -2103,10 +2103,11 @@ fn analyze_accepts_function_with_exactly_4_params() {
|
|||
|
||||
#[test]
|
||||
fn analyze_allows_same_local_name_in_two_functions() {
|
||||
// Regression test for COMPILER_BUGS.md §3: function-body
|
||||
// `var` declarations used to live in a flat global namespace,
|
||||
// so two functions both declaring `var i` collided on E0501.
|
||||
// They should now coexist in their own per-function scopes.
|
||||
// Regression test for the function-local scope-qualification
|
||||
// fix on the War bug-cleanup branch (see `git log`): function-
|
||||
// body `var` declarations used to live in a flat global
|
||||
// namespace, so two functions both declaring `var i` collided
|
||||
// on E0501. They now coexist in per-function scopes.
|
||||
analyze_ok(
|
||||
r#"
|
||||
game "T" { mapper: NROM }
|
||||
|
|
@ -2132,11 +2133,12 @@ fn analyze_allows_same_local_name_in_two_functions() {
|
|||
|
||||
#[test]
|
||||
fn analyze_allows_same_param_name_in_two_functions() {
|
||||
// Regression test for COMPILER_BUGS.md §1b: parameters
|
||||
// Regression test for the scope-qualified parameter fix on
|
||||
// the War bug-cleanup branch (see `git log`): parameters
|
||||
// across different functions used to share VarIds because
|
||||
// the IR lowerer's `var_map` was global. Both declaration
|
||||
// (analyzer) and lowering (IR) should now give each
|
||||
// function's parameters their own independent entries.
|
||||
// (analyzer) and lowering (IR) now give each function's
|
||||
// parameters their own independent entries.
|
||||
analyze_ok(
|
||||
r#"
|
||||
game "T" { mapper: NROM }
|
||||
|
|
@ -2477,3 +2479,145 @@ fn analyze_accepts_cycle_sprites_statement() {
|
|||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_inline_fun_with_conditional_return_trips_w0110() {
|
||||
// A function marked `inline` whose body has an early
|
||||
// conditional return can't be inlined by the simple
|
||||
// substitution machinery — it compiles as a regular
|
||||
// `JSR` call, and W0110 must fire at the declaration.
|
||||
let (prog, diags) = parser::parse(
|
||||
r#"
|
||||
game "T" { mapper: NROM }
|
||||
inline fun wrap(v: u8) -> u8 {
|
||||
if v >= 52 {
|
||||
return v - 52
|
||||
}
|
||||
return v
|
||||
}
|
||||
var x: u8 = 0
|
||||
on frame {
|
||||
x = wrap(60)
|
||||
wait_frame
|
||||
}
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
assert!(diags.is_empty(), "parse errors: {diags:?}");
|
||||
let result = analyze(&prog.unwrap());
|
||||
let w0110: Vec<_> = result
|
||||
.diagnostics
|
||||
.iter()
|
||||
.filter(|d| d.code == ErrorCode::W0110)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
w0110.len(),
|
||||
1,
|
||||
"expected exactly one W0110, got: {:?}",
|
||||
result.diagnostics
|
||||
);
|
||||
assert!(
|
||||
w0110[0].message.contains("wrap"),
|
||||
"W0110 should name the declined function, got: {}",
|
||||
w0110[0].message
|
||||
);
|
||||
assert!(
|
||||
w0110[0].help.is_some() && w0110[0].note.is_some(),
|
||||
"W0110 should carry both help and note text"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_inline_fun_with_single_return_expression_is_accepted() {
|
||||
// A body that is exactly `return <expr>` is the canonical
|
||||
// inlinable shape — no W0110 should fire.
|
||||
let (prog, diags) = parser::parse(
|
||||
r#"
|
||||
game "T" { mapper: NROM }
|
||||
inline fun card_rank(card: u8) -> u8 {
|
||||
return card >> 4
|
||||
}
|
||||
var x: u8 = 0
|
||||
on frame {
|
||||
x = card_rank(0x93)
|
||||
wait_frame
|
||||
}
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
assert!(diags.is_empty(), "parse errors: {diags:?}");
|
||||
let result = analyze(&prog.unwrap());
|
||||
assert!(
|
||||
!result
|
||||
.diagnostics
|
||||
.iter()
|
||||
.any(|d| d.code == ErrorCode::W0110),
|
||||
"did not expect W0110 for single-return inline, got: {:?}",
|
||||
result.diagnostics
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_inline_void_fun_is_accepted() {
|
||||
// A void body with nothing but assigns is inlinable.
|
||||
let (prog, diags) = parser::parse(
|
||||
r#"
|
||||
game "T" { mapper: NROM }
|
||||
var a: u8 = 0
|
||||
var b: u8 = 0
|
||||
inline fun set_pair(x: u8, y: u8) {
|
||||
a = x
|
||||
b = y
|
||||
}
|
||||
on frame {
|
||||
set_pair(5, 6)
|
||||
wait_frame
|
||||
}
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
assert!(diags.is_empty(), "parse errors: {diags:?}");
|
||||
let result = analyze(&prog.unwrap());
|
||||
assert!(
|
||||
!result
|
||||
.diagnostics
|
||||
.iter()
|
||||
.any(|d| d.code == ErrorCode::W0110),
|
||||
"did not expect W0110 for void inline, got: {:?}",
|
||||
result.diagnostics
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_inline_fun_with_loop_body_trips_w0110() {
|
||||
// A `while` loop inside a marked-inline body is another
|
||||
// disqualifying shape.
|
||||
let (prog, diags) = parser::parse(
|
||||
r#"
|
||||
game "T" { mapper: NROM }
|
||||
var sum: u8 = 0
|
||||
inline fun accumulate(n: u8) {
|
||||
var i: u8 = 0
|
||||
while i < n {
|
||||
sum += i
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
on frame {
|
||||
accumulate(5)
|
||||
wait_frame
|
||||
}
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
assert!(diags.is_empty(), "parse errors: {diags:?}");
|
||||
let result = analyze(&prog.unwrap());
|
||||
assert!(
|
||||
result
|
||||
.diagnostics
|
||||
.iter()
|
||||
.any(|d| d.code == ErrorCode::W0110),
|
||||
"expected W0110 for loop-body inline, got: {:?}",
|
||||
result.diagnostics
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -242,9 +242,11 @@ impl<'a> IrCodeGen<'a> {
|
|||
// Before this change, parameters lived in `$04-$07` for the
|
||||
// duration of the function body, so any call nested inside
|
||||
// a function's body silently corrupted the caller's
|
||||
// parameters — see COMPILER_BUGS.md §2. The per-function
|
||||
// RAM slots + prologue spill fix that class of bug at the
|
||||
// cost of 4 LDA/STA pairs per function entry.
|
||||
// parameters (fixed on the War bug-cleanup branch; see
|
||||
// `git log` for the original reproduction and root cause).
|
||||
// The per-function RAM slots + prologue spill fix that
|
||||
// class of bug at the cost of 4 LDA/STA pairs per function
|
||||
// entry.
|
||||
//
|
||||
// Locals are laid out linearly across every function:
|
||||
// NEScript forbids recursion (E0402) and enforces a
|
||||
|
|
@ -3906,7 +3908,8 @@ mod more_tests {
|
|||
|
||||
#[test]
|
||||
fn gen_function_prologue_spills_params_to_local_ram() {
|
||||
// Regression test for COMPILER_BUGS.md §2: without the
|
||||
// Regression test for the param-slot clobbering bug fixed
|
||||
// on the War bug-cleanup branch (see `git log`): without the
|
||||
// param-spill prologue, a function's parameters live only
|
||||
// in the transport slots $04-$07. The first nested call
|
||||
// inside the body would overwrite those slots with its
|
||||
|
|
@ -3969,7 +3972,7 @@ fn gen_function_prologue_spills_params_to_local_ram() {
|
|||
assert!(
|
||||
saw_lda_zp4 && saw_sta_abs,
|
||||
"caller function should open with `LDA $04 / STA <absolute>` \
|
||||
as the param-spill prologue — the fix for COMPILER_BUGS.md §2 \
|
||||
is not in effect"
|
||||
as the param-spill prologue — the param-clobbering fix is \
|
||||
not in effect"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ pub enum ErrorCode {
|
|||
W0107, // `fast` variable rarely accessed (wastes zero-page slot)
|
||||
W0108, // array elements past byte 255 unreachable via 8-bit X index
|
||||
W0109, // too many literal-coord sprite draws on one scanline (NES 8/scanline limit)
|
||||
W0110, // `inline fun` declined — body shape not splicable, fell back to regular call
|
||||
}
|
||||
|
||||
impl fmt::Display for ErrorCode {
|
||||
|
|
@ -74,6 +75,7 @@ impl fmt::Display for ErrorCode {
|
|||
Self::W0107 => "W0107",
|
||||
Self::W0108 => "W0108",
|
||||
Self::W0109 => "W0109",
|
||||
Self::W0110 => "W0110",
|
||||
};
|
||||
write!(f, "{code}")
|
||||
}
|
||||
|
|
@ -90,7 +92,8 @@ impl ErrorCode {
|
|||
| Self::W0106
|
||||
| Self::W0107
|
||||
| Self::W0108
|
||||
| Self::W0109 => Level::Warning,
|
||||
| Self::W0109
|
||||
| Self::W0110 => Level::Warning,
|
||||
_ => Level::Error,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ struct LoweringContext {
|
|||
/// registered as substitutions for the parameter names,
|
||||
/// and the body is lowered into the caller's current block
|
||||
/// in place of a `Call` op. See `try_inline_call_expr` /
|
||||
/// `try_inline_call_stmt` below and `COMPILER_BUGS.md` §5.
|
||||
/// `try_inline_call_stmt` below; the feature was added on
|
||||
/// the War bug-cleanup branch (see `git log`).
|
||||
inline_bodies: HashMap<String, CapturedInline>,
|
||||
/// Substitution stack for nested inline expansions. The top
|
||||
/// frame is the active substitution map — `Expr::Ident(name)`
|
||||
|
|
@ -237,11 +238,18 @@ impl LoweringContext {
|
|||
if !fun.is_inline {
|
||||
continue;
|
||||
}
|
||||
// Defer to the shared `can_inline_fun` helper so the
|
||||
// analyzer's W0110 check and this capture pass agree
|
||||
// on exactly which bodies are splicable — any drift
|
||||
// between the two would either swallow real bugs
|
||||
// (lower inlines a body the analyzer thinks it
|
||||
// wouldn't) or emit false-positive warnings (analyzer
|
||||
// warns on something the lowerer actually inlines).
|
||||
if !can_inline_fun(fun.return_type.as_ref(), &fun.body) {
|
||||
continue;
|
||||
}
|
||||
// Single-return-expression shape.
|
||||
if fun.return_type.is_some()
|
||||
&& fun.body.statements.len() == 1
|
||||
&& matches!(fun.body.statements[0], Statement::Return(Some(_), _))
|
||||
{
|
||||
if fun.return_type.is_some() && fun.body.statements.len() == 1 {
|
||||
if let Statement::Return(Some(expr), _) = &fun.body.statements[0] {
|
||||
self.inline_bodies.insert(
|
||||
fun.name.clone(),
|
||||
|
|
@ -253,25 +261,16 @@ impl LoweringContext {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
// Void multi-statement shape: no return type, and
|
||||
// every body statement must be a shape we know how
|
||||
// to splice. Only assigns, statement-context calls,
|
||||
// draws, scroll, set_palette, and load_background
|
||||
// are accepted — anything with nested control flow
|
||||
// is too complex to inline without a full CFG
|
||||
// clone.
|
||||
if fun.return_type.is_none()
|
||||
&& !fun.body.statements.is_empty()
|
||||
&& fun.body.statements.iter().all(is_splicable_void_stmt)
|
||||
{
|
||||
self.inline_bodies.insert(
|
||||
fun.name.clone(),
|
||||
CapturedInline {
|
||||
params: fun.params.clone(),
|
||||
body: InlineBody::Void(fun.body.statements.clone()),
|
||||
},
|
||||
);
|
||||
}
|
||||
// Void multi-statement shape: no return type, every
|
||||
// body statement is splicable. The helper already
|
||||
// checked both conditions so we just clone.
|
||||
self.inline_bodies.insert(
|
||||
fun.name.clone(),
|
||||
CapturedInline {
|
||||
params: fun.params.clone(),
|
||||
body: InlineBody::Void(fun.body.statements.clone()),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -626,7 +625,9 @@ impl LoweringContext {
|
|||
// true and, worse, `widen(t)` returning a stale `hi` temp ID
|
||||
// that collides with a later `fresh_temp()` allocation —
|
||||
// producing 16-bit IR ops where the destination temp is
|
||||
// *also* one of the source temps. See COMPILER_BUGS.md §6.
|
||||
// *also* one of the source temps (the `wide_hi` leak bug
|
||||
// fixed on the War cleanup branch; see `git log` for the
|
||||
// full reproduction).
|
||||
self.wide_hi.clear();
|
||||
self.current_blocks = Vec::new();
|
||||
self.current_locals = Vec::new();
|
||||
|
|
@ -724,9 +725,9 @@ impl LoweringContext {
|
|||
fn lower_handler(&mut self, name: &str, scope_prefix: &str, block: &Block, state: &StateDecl) {
|
||||
self.next_temp = 0;
|
||||
// Same per-function reset as `lower_function`. See the
|
||||
// commentary there and COMPILER_BUGS.md §6 for why this is
|
||||
// critical — without it, state-handler bodies pick up wide
|
||||
// temp pairs left over from the previous function and emit
|
||||
// commentary there for why this is critical — without it,
|
||||
// state-handler bodies pick up wide temp pairs left over
|
||||
// from the previous function and emit
|
||||
// catastrophically wrong 16-bit IR ops.
|
||||
self.wide_hi.clear();
|
||||
self.current_blocks = Vec::new();
|
||||
|
|
@ -1837,6 +1838,40 @@ fn is_splicable_void_stmt(stmt: &Statement) -> bool {
|
|||
)
|
||||
}
|
||||
|
||||
/// True if an `inline fun` with the given return type and body
|
||||
/// matches one of the shapes [`LoweringContext::capture_inline_bodies`]
|
||||
/// can splice into a caller. Two shapes are recognised:
|
||||
///
|
||||
/// 1. **Single-return expression**: the function has a declared
|
||||
/// return type and its body is exactly `{ return <expr> }`.
|
||||
/// 2. **Void multi-statement**: the function has no return type
|
||||
/// and every body statement passes [`is_splicable_void_stmt`].
|
||||
///
|
||||
/// Anything else (conditional early returns, loops, nested
|
||||
/// control flow, multiple returns, an empty void body) falls back
|
||||
/// to a regular `JSR` call at every site. The analyzer calls this
|
||||
/// to emit `W0110` when a declared-inline function won't actually
|
||||
/// be inlined, and the IR lowerer calls the same logic when it
|
||||
/// decides which bodies to capture — keeping both sides in sync.
|
||||
#[must_use]
|
||||
pub fn can_inline_fun(return_type: Option<&NesType>, body: &Block) -> bool {
|
||||
// Single-return expression shape.
|
||||
if return_type.is_some()
|
||||
&& body.statements.len() == 1
|
||||
&& matches!(body.statements[0], Statement::Return(Some(_), _))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Void multi-statement shape.
|
||||
if return_type.is_none()
|
||||
&& !body.statements.is_empty()
|
||||
&& body.statements.iter().all(is_splicable_void_stmt)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn type_size(t: &NesType) -> u16 {
|
||||
match t {
|
||||
NesType::U8 | NesType::I8 | NesType::Bool => 1,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ mod lowering;
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use lowering::{lower, RAW_ASM_PREFIX};
|
||||
pub use lowering::{can_inline_fun, lower, RAW_ASM_PREFIX};
|
||||
|
||||
use crate::lexer::Span;
|
||||
use std::fmt;
|
||||
|
|
|
|||
|
|
@ -743,7 +743,8 @@ fn lower_modulo_emits_mod_op_not_load_imm_zero() {
|
|||
|
||||
#[test]
|
||||
fn wide_hi_does_not_leak_between_functions() {
|
||||
// Regression test for COMPILER_BUGS.md §6: the IR lowerer's
|
||||
// Regression test for the `wide_hi` leak bug fixed on the
|
||||
// War bug-cleanup branch (see `git log`): the IR lowerer's
|
||||
// `wide_hi` map used to persist across function boundaries
|
||||
// even though `next_temp` resets to 0 per function. A
|
||||
// function whose body had no u16 ops would inherit stale
|
||||
|
|
@ -790,7 +791,7 @@ fn wide_hi_does_not_leak_between_functions() {
|
|||
{
|
||||
// The dest of a 16-bit compare must never alias one
|
||||
// of its operand high bytes — that's the symptom of
|
||||
// bug #6 from war/COMPILER_BUGS.md.
|
||||
// the `wide_hi` leak bug.
|
||||
if dest == b_hi || dest == a_hi {
|
||||
wide_eq_dest_aliases += 1;
|
||||
}
|
||||
|
|
@ -804,7 +805,8 @@ fn wide_hi_does_not_leak_between_functions() {
|
|||
|
||||
#[test]
|
||||
fn inline_fun_expression_body_emits_no_call_at_use_site() {
|
||||
// Regression test for COMPILER_BUGS.md §5: `inline fun`
|
||||
// Regression test for the real-inlining feature added on
|
||||
// the War bug-cleanup branch (see `git log`): `inline fun`
|
||||
// with a single-return-expression body should be spliced
|
||||
// at every call site instead of emitting a Call op. The
|
||||
// lowered frame handler should contain zero Call ops
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue