mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
Analyzer: E0301 error on RAM exhaustion
The linear RAM allocator now checks for overflow. The zero-page region is capped at \$80 (leaving \$80-\$FF for IR codegen temp slots), and the main RAM allocator stops at \$0800 (end of NES internal RAM). Overflow emits E0301 with a helpful note pointing at the declaration. https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
parent
1e4e384501
commit
e688397594
3 changed files with 72 additions and 11 deletions
|
|
@ -36,6 +36,15 @@ pub struct AnalysisResult {
|
|||
/// Default call stack depth limit for the NES runtime.
|
||||
const DEFAULT_STACK_DEPTH: u32 = 8;
|
||||
|
||||
/// Upper bound (exclusive) for user-variable zero-page allocation.
|
||||
/// Addresses `$80-$FF` are reserved for IR codegen temp slots, so user
|
||||
/// globals must fit into `$10-$7F`.
|
||||
const ZP_USER_CAP: u8 = 0x80;
|
||||
|
||||
/// Exclusive upper bound of usable RAM. The NES has 2 KB of internal
|
||||
/// RAM at `$0000-$07FF`; the allocator uses up through `$07FF`.
|
||||
const RAM_END: u16 = 0x0800;
|
||||
|
||||
/// Analyze a parsed program for semantic errors.
|
||||
pub fn analyze(program: &Program) -> AnalysisResult {
|
||||
let mut analyzer = Analyzer {
|
||||
|
|
@ -403,7 +412,21 @@ impl Analyzer {
|
|||
}
|
||||
|
||||
let size = type_size(&var.var_type);
|
||||
let address = self.allocate_ram(size);
|
||||
let Some(address) = self.allocate_ram(size, var.span) else {
|
||||
// Allocation failed (E0301 already emitted) — still add the
|
||||
// symbol so that later references don't cascade into E0502,
|
||||
// but don't record a var_allocations entry.
|
||||
self.symbols.insert(
|
||||
var.name.clone(),
|
||||
Symbol {
|
||||
name: var.name.clone(),
|
||||
sym_type: var.var_type.clone(),
|
||||
is_const: false,
|
||||
span: var.span,
|
||||
},
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
self.symbols.insert(
|
||||
var.name.clone(),
|
||||
|
|
@ -446,17 +469,38 @@ impl Analyzer {
|
|||
.insert(fun.name.clone(), param_types);
|
||||
}
|
||||
|
||||
fn allocate_ram(&mut self, size: u16) -> u16 {
|
||||
// For M1: simple linear allocator using zero-page for u8 vars
|
||||
if size == 1 && self.next_zp_addr < 0xFF {
|
||||
/// Attempt to allocate `size` bytes of RAM for a variable declared
|
||||
/// at `span`. Returns `None` on overflow, emitting E0301. The
|
||||
/// zero-page user region is bounded above by [`ZP_USER_CAP`] to
|
||||
/// leave room for IR codegen temp slots starting at $80.
|
||||
fn allocate_ram(&mut self, size: u16, span: Span) -> Option<u16> {
|
||||
// Zero-page u8 allocation — bounded by ZP_USER_CAP to avoid
|
||||
// colliding with the IR temp region at $80+.
|
||||
if size == 1 && self.next_zp_addr < ZP_USER_CAP {
|
||||
let addr = u16::from(self.next_zp_addr);
|
||||
self.next_zp_addr = self.next_zp_addr.wrapping_add(1);
|
||||
addr
|
||||
} else {
|
||||
let addr = self.next_ram_addr;
|
||||
self.next_ram_addr += size;
|
||||
addr
|
||||
return Some(addr);
|
||||
}
|
||||
|
||||
// Larger / remaining allocations go into the main RAM region
|
||||
// after the OAM buffer.
|
||||
let end = self.next_ram_addr.checked_add(size)?;
|
||||
if end > RAM_END {
|
||||
self.diagnostics.push(
|
||||
Diagnostic::error(
|
||||
ErrorCode::E0301,
|
||||
"out of RAM: too many variables declared",
|
||||
span,
|
||||
)
|
||||
.with_help(
|
||||
"the NES only has 2 KB of RAM ($0000-$07FF); consider removing some globals",
|
||||
),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let addr = self.next_ram_addr;
|
||||
self.next_ram_addr = end;
|
||||
Some(addr)
|
||||
}
|
||||
|
||||
fn build_call_graph(&mut self, program: &Program) {
|
||||
|
|
|
|||
|
|
@ -298,6 +298,24 @@ fn analyze_return_wrong_type() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_ram_overflow_emits_e0301() {
|
||||
// Two arrays totalling >2 KB cannot fit in NES RAM, triggering
|
||||
// E0301 at allocation time.
|
||||
let src = r#"
|
||||
game "Test" { mapper: NROM }
|
||||
var huge: u8[2000]
|
||||
var also_huge: u8[2000]
|
||||
on frame { wait_frame }
|
||||
start Main
|
||||
"#;
|
||||
let errors = analyze_errors(src);
|
||||
assert!(
|
||||
errors.contains(&ErrorCode::E0301),
|
||||
"RAM overflow should produce E0301, got: {errors:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_expensive_multiply_warns() {
|
||||
let errors = analyze_errors(
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@ pub enum ErrorCode {
|
|||
E0203, // invalid operation for type
|
||||
|
||||
// E03xx: Memory errors
|
||||
#[allow(dead_code)]
|
||||
E0301, // zero-page overflow
|
||||
E0301, // zero-page overflow / RAM exhausted
|
||||
|
||||
// E04xx: Control flow errors
|
||||
E0401, // call depth exceeded
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue