diff --git a/README.markdown b/README.markdown index 1d8a63a..c3c5f78 100644 --- a/README.markdown +++ b/README.markdown @@ -83,6 +83,13 @@ Along with routines, you get `if`, `repeat`, and `with` constructs which take blocks. The `with` construct takes an instruction like `sei` and implicitly (and unavoidably) inserts the corresponding `cli` at the end of the block. +Abstract interpretation extends to `if` blocks. The two incoming contexts are +merged, and any storage locations poisoned in either context are considered +poisoned in the result context. + +(Same should apply for `repeat` and `with` and, really, many other cases +which there just aren't enough test cases for yet.) + For More Information -------------------- @@ -94,11 +101,6 @@ Ideas ----- These aren't implemented yet: - -* Abstract interpretation must extend to `if`, `repeat`, and `with` - blocks. The two incoming contexts must be merged, and any storage - locations updated differently or poisoned in either context, will be - considered poisoned in the result context. * Inside a routine, an address may be declared with `temporary`. This is like `static` in C, except the value at that address is not guaranteed to be diff --git a/doc/Analyzing.markdown b/doc/Analyzing.markdown index 7f9d2f7..c609775 100644 --- a/doc/Analyzing.markdown +++ b/doc/Analyzing.markdown @@ -127,3 +127,72 @@ Routines can name registers as outputs. = = update_score ([A]) = A: UpdatedWith (Immediate 8) + +If a location is poisoned in either branch of an `if`, it is poisoned +after the `if`. + + | reserve byte score + | routine update_score + | { + | if beq { + | lda #8 + | } else { + | ldx #8 + | } + | } + | routine main { + | lda #4 + | jsr update_score + | sta score + | } + ? routine does not preserve 'A' + + | reserve byte score + | routine update_score + | { + | if beq { + | ldx #8 + | } else { + | lda #8 + | } + | } + | routine main { + | lda #4 + | jsr update_score + | sta score + | } + ? routine does not preserve 'A' + + | reserve byte score + | routine update_score + | { + | lda #4 + | sta score + | } + | routine main { + | lda #4 + | if beq { + | jsr update_score + | } else { + | ldx #3 + | } + | sta score + | } + ? routine does not preserve 'A' + + | reserve byte score + | routine update_score + | { + | lda #4 + | sta score + | } + | routine main { + | lda #4 + | if beq { + | ldx #3 + | } else { + | jsr update_score + | } + | sta score + | } + ? routine does not preserve 'A' diff --git a/src/SixtyPical/Analyzer.hs b/src/SixtyPical/Analyzer.hs index bc4a4da..a4e4539 100644 --- a/src/SixtyPical/Analyzer.hs +++ b/src/SixtyPical/Analyzer.hs @@ -73,10 +73,11 @@ analyzeProgram program@(Program decls routines) = -- TODO: mark Carry bit as "touched" here routCtx checkInstr (IF _ branch b1 b2) progCtx routCtx = - -- TODO: oooh, this one's gonna be fun - --checkBlock b1 progCtx routCtx - --checkBlock b2 progCtx routCtx - routCtx + let + routCtx1 = checkBlock b1 progCtx routCtx + routCtx2 = checkBlock b2 progCtx routCtx + in + mergeAlternateRoutCtxs routCtx1 routCtx2 checkInstr (REPEAT _ branch blk) progCtx routCtx = -- TODO: oooh, this one's gonna be fun too --checkBlock blk progCtx routCtx @@ -140,3 +141,29 @@ untypedLocation (NamedLocation (Just _) name) = NamedLocation Nothing name untypedLocation x = x +-- +-- Utility function: +-- Take 2 routine contexts -- one from each branch of an `if` -- and merge +-- them to create a new context for the remainder of the routine. +-- +mergeAlternateRoutCtxs routCtx1 routCtx2 = + let + -- go through all the Usages in routCtx2 + -- insert any that were updated, into routCtx1 + poison location usage2 routCtxAccum = + case Map.lookup location routCtx1 of + Nothing -> + Map.insert location usage2 routCtxAccum + Just usage1 -> + -- it exists in both routCtxs. + -- if it is poisoned in either, it's poisoned here. + -- otherwise, it is OK to differ. + let + newUsage = case (usage1, usage2) of + (PoisonedWith _, _) -> usage1 + (_, PoisonedWith _) -> usage2 + _ -> usage1 -- or 2. doesn't matter. + in + Map.insert location newUsage routCtxAccum + in + Map.foldrWithKey (poison) routCtx1 routCtx2