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
32 changes: 28 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ HAS bridges the gap between high-level languages and assembly programming. It of

### Advanced Features

- **Macro System (Phase 2)**: Define reusable code patterns
- **@python Directive (Phase 4)**: Execute Python code during compilation
- **Macro System**: Define reusable code patterns
- **@python Directive**: Execute Python code during compilation
- **Include System**: Modular code organization with `#include`
- **Constants**: Compile-time constant evaluation
- **Pointer Arithmetic**: Address-of (`&`) and dereference (`*`) operators
- **Register Locking**: `#pragma lockreg()` to protect registers from compiler allocation
- **Dead-Code Elimination**: `--strip-unused-procs` removes unreachable internal procedures before assembly emission

### Amiga-Specific

Expand Down Expand Up @@ -188,7 +189,7 @@ These examples demonstrate game-related concepts and systems, but they are not r

### Code Generation

- `code_generator.py` - External Python code generation (Phase 1)
- `code_generator.py` - External Python code generation
- `simple_generator.py` - Simple generation example

## 🔧 Usage
Expand All @@ -199,7 +200,7 @@ These examples demonstrate game-related concepts and systems, but they are not r
python -m hasc.cli input.has -o output.s
```

### With External Code Generation (Phase 1)
### With External Code Generation

```bash
python -m hasc.cli main.has --generate generator.py -o output.s
Expand All @@ -225,6 +226,29 @@ if __name__ == "__main__":
python -m hasc.cli input.has --no-validate -o output.s
```

### Remove Unused Procedures (dead-code elimination)

```bash
# Remove unreachable internal procedures before assembly emission
python -m hasc.cli program.has --strip-unused-procs -o program.s

# Same, but also print what was removed
python -m hasc.cli program.has --strip-unused-procs --strip-unused-report -o program.s
```

The pass uses call-graph reachability from `public` declarations. It is
**conservative by default**: if no roots are found, or if a top-level raw
`asm` block is present, all procedures are kept unchanged.
Comment on lines +239 to +241

```has
// Mark the entry point so unreachable procs can be stripped
public game_init;

proc game_init() -> void { ... } // kept (root)
proc helper() -> void { ... } // kept (called by game_init)
proc dead_code() -> void { ... } // removed (never called)
```

### Build Complete Executable

```bash
Expand Down
17 changes: 14 additions & 3 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ All notable changes to the HAS (High Assembler) project will be documented in th

### Added

- **Dead-procedure elimination pass** (`--strip-unused-procs` / `--strip-unused-report`):
- New module `hasc/reachability.py` performs conservative call-graph analysis after validation and before code generation.
- Roots are discovered from `public` declarations that point to internal `proc` definitions.
- Unreachable internal procedures are removed from the AST before assembly is emitted.
- Three conservative keep-all safeguards prevent incorrect stripping:
- **Feature off by default** — requires an explicit opt-in flag.
- **Top-level asm block** — raw `jsr`/`jmp` may reference any label; all procs kept.
- **No roots found** — keeps everything rather than silently discarding all code.
- `--strip-unused-report` prints roots, kept, and removed procedure lists to stderr.
- Three new example files demonstrate all scenarios: `strip_unused_procs_demo.has`, `strip_unused_procs_asm_safe.has`, `strip_unused_procs_no_roots.has`.

- **Example suite split gate** for deterministic regression checks:
- Added `examples/negative_examples.txt` manifest for expected-fail examples.
- Added `scripts/test_examples_split.sh` to enforce:
Expand Down Expand Up @@ -163,9 +174,9 @@ All notable changes to the HAS (High Assembler) project will be documented in th
- Increment/Decrement: ++, --

#### Advanced Features
- **Phase 1**: External Python code generation via `--generate` flag
- **Phase 2**: Macro system with parameter substitution
- **Phase 3**: Inline Python execution with `@python` directive
- External Python code generation via `--generate` flag
- Macro system with parameter substitution
- Inline Python execution with `@python` directive
- Include system with cyclic dependency detection
- Inline assembly support with `asm { }` blocks
- Register manipulation with `getreg()` and `setreg()` intrinsics
Expand Down
5 changes: 4 additions & 1 deletion docs/COMPILER_DEVELOPERS_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ src/hasc/
[Validator] ───→ Validated AST + Warnings
[Reachability] → Pruned AST (opt-in; see --strip-unused-procs)
[CodeGen] ─────→ 68000 Assembly Text
vasm/vlink ───→ Executable Binary
Expand All @@ -65,6 +67,7 @@ src/hasc/

- Sections: `data`/`data_chip`, `bss`/`bss_chip`, `code`/`code_chip`, inline `asm`
- Compile-time directives: `#warning`, `#error`, `#pragma lockreg(...)`, `#pragma strict16arith(on|off)`, `const` declarations
- Dead-code elimination: `--strip-unused-procs` removes unreachable internal `proc` definitions via call-graph analysis (`hasc/reachability.py`)
- Procedure system: `proc`, forward `func` declarations, `extern func/var`, `public` exports
- Control flow and expressions: loops (`for`/`while`/`repeat`), conditionals, full operator set including shifts and bitwise ops
- Python integration: macros, `@python` directives, optional external generation via `--generate`
Expand Down Expand Up @@ -1322,7 +1325,7 @@ When working on the compiler, always:
### Internal Documentation

- [README.md](README.md) - Project overview
- [QUICK_START_ALL_PHASES.md](QUICK_START_ALL_PHASES.md) - Phase implementation guide
- [QUICK_START_ALL_PHASES.md](QUICK_START_ALL_PHASES.md) - Implementation guide
- [OPERATORS.md](OPERATORS.md) - Operator precedence and implementation
- [PROC_VS_FUNC_SUMMARY.md](PROC_VS_FUNC_SUMMARY.md) - Function declaration patterns

Expand Down
6 changes: 3 additions & 3 deletions docs/DEVELOPERS_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,7 @@ code increment:

## Advanced Features

### Macros (Phase 2)
### Macros
```has
; Define reusable code patterns
macro load_register(reg, value) {
Expand All @@ -775,7 +775,7 @@ code macro_demo:
}
```

### Python Directives (Phase 3)
### Python Directives
```has
code python_demo:
proc computed() -> long {
Expand All @@ -790,7 +790,7 @@ code python_demo:
}
```

### External Code Generation (Phase 1)
### External Code Generation
Create `generator.py`:
```python
#!/usr/bin/env python3
Expand Down
10 changes: 5 additions & 5 deletions docs/PYTHON_GENERATION_TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,13 +429,13 @@ with open('/tmp/debug.has', 'w') as f:
1. Try running the example: `python3 examples/code_generator.py`
2. Create your own generator for a specific use case
3. Integrate into your build process
4. Consider implementing Phase 2 (macros) if needed
4. Consider adding macros if needed

---

## What's Next: Phase 2
## What's Next

- **Phase 2**: `@macro` directives for repetitive patterns
- **Phase 3**: `@python` inline code blocks (requires sandbox)
- `@macro` directives for repetitive patterns
- `@python` inline code blocks (requires sandbox)

For now, Phase 1 (external Python) gives you full power with no changes to the compiler core!
For now, external Python generation gives you full power with no changes to the compiler core.
14 changes: 7 additions & 7 deletions docs/PYTHON_INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,26 +271,26 @@ if args.generate:

## Recommended Implementation Path

### Phase 1: External Python (Quick Win)
### External Python (Quick Win)
- Implement `--generate` CLI option
- Use subprocess to run Python scripts
- Allows users to generate HAS files before compilation
- No changes to compiler needed

### Phase 2: Macro System (Foundation)
### Macro System (Foundation)
- Add macro definitions to grammar
- Implement macro expansion
- Perfect for repetitive patterns

### Phase 3: Python Sandbox (Advanced)
### Python Sandbox (Advanced)
- Add `@python` directive
- Implement safe execution sandbox
- Full compile-time code generation
- Complex patterns possible

---

## Detailed Implementation: Phase 1 (Start Here)
## Detailed Implementation: External Python (Start Here)

### 1. Modify CLI

Expand Down Expand Up @@ -446,7 +446,7 @@ for op in ['add', 'sub', 'mul']:

## Recommendation

**Start with Phase 1 (External Python):**
**Start with external Python integration:**

1. **Implement `--generate` option** in CLI (30 minutes)
- Add argument parsing
Expand All @@ -467,7 +467,7 @@ for op in ['add', 'sub', 'mul']:
- ✅ Minimal changes to HAS core
- ✅ Full Python power immediately
- ✅ Easy for users to understand
- ✅ Can extend to Phases 2-4 later
- ✅ Can extend with macro and sandbox features later
- ✅ No security sandbox needed
- ✅ Can debug generated code easily

Expand All @@ -477,7 +477,7 @@ Then later add `@macro` directive as needed.

## Next Steps

1. Would you like me to implement Phase 1 (`--generate` option)?
1. Would you like me to implement the `--generate` option?
2. Should we add `@macro` support alongside?
3. Any specific use case you have in mind?

Expand Down
10 changes: 5 additions & 5 deletions docs/STRUCT_POINTER_IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ var x = (*p).x; // Read member through pointer

## Implementation Plan

### Phase 1: Type System Enhancement
### Type System Enhancement

#### 1.1 Update ast.py

Expand Down Expand Up @@ -83,7 +83,7 @@ def pointer_base_type(typename: str) -> Optional[str]:
return None
```

### Phase 2: Parser Updates
### Parser Updates

The parser already supports pointer types in variable declarations through the `type` rule. However, the `lvalue` grammar rule needs to be extended to support dereferenced struct member access.

Expand Down Expand Up @@ -147,7 +147,7 @@ def lvalue(self, items):
# ... rest of existing code
```

### Phase 3: Semantic Validation
### Semantic Validation

Add validation to ensure struct types are valid.

Expand Down Expand Up @@ -187,7 +187,7 @@ def _validate_data_section(self, section):
self.struct_types.add(item.name)
```

### Phase 4: Code Generation
### Code Generation

The code generator already handles:
1. Address-of for array elements: `&bullet[i]`
Expand Down Expand Up @@ -246,7 +246,7 @@ Ensure `vtype` is stored as the string `"bullet*"` (not processed or stripped).

Looking at the code, the VarDecl AST node has a `vtype` field that should contain the full type string including the `*`.

### Phase 5: Testing
### Testing

#### 5.1 Create Test File

Expand Down
12 changes: 11 additions & 1 deletion examples/games/caveride/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ VASM := vasmm68k_mot
VLINK := vlink
WHICH := which
HASC := $(PYTHON) -m hasc.cli
HASC_FLAGS := --strip-unused-procs --strip-unused-report

# Sources and targets
# Main has file (must be first for linker)
Expand All @@ -52,6 +53,13 @@ ASSET_OBJS := $(ASSET_SRCS:$(SRC_DIR)/%.s=$(BUILD_DIR)/%.o)
VASM_FLAGS := -m68000 -Fhunk -kick1hunks -I$(LIB_DIR)
VLINK_FLAGS := -bamigahunk -Bstatic

# Strip symbols from the final executable by default.
# Use STRIP=0 for debug builds that keep symbols.
STRIP ?= 1
ifeq ($(STRIP),1)
VLINK_FLAGS += -s
endif

.PHONY: all clean compile assemble link run help libs game-objs asset-objs

all: $(BUILD_DIR) $(EXE_FILE)
Expand All @@ -75,7 +83,7 @@ $(BUILD_DIR)/%.o: %.has | $(BUILD_DIR)
echo "Error: Python interpreter '$(PYTHON)' not found. Set up venv or install python3."; \
exit 1; \
fi
PYTHONPATH="$(PYTHONPATH)" "$(PYTHON)" -m hasc.cli "$(abspath $<)" -o "$(abspath $(BUILD_DIR)/$*.s)"
PYTHONPATH="$(PYTHONPATH)" $(HASC) $(HASC_FLAGS) "$(abspath $<)" -o "$(abspath $(BUILD_DIR)/$*.s)"
$(VASM) $(VASM_FLAGS) -L $(BUILD_DIR)/$*.lst -o $@ $(BUILD_DIR)/$*.s

$(MAIN_OBJ): $(MAIN_HAS) | $(BUILD_DIR)
Expand Down Expand Up @@ -104,6 +112,8 @@ asset-objs: $(ASSET_OBJS)

help:
@echo "Targets: all (default), compile, assemble, link, libs, clean, run, help"
@echo "Options: STRIP=1 (default, strips symbols), STRIP=0 (keep symbols)"
@echo "Compiler flags: HASC_FLAGS='$(HASC_FLAGS)' (override to customize)"

clean:
rm -rf $(BUILD_DIR)/*.o $(BUILD_DIR)/*.s $(BUILD_DIR)/*.exe $(BUILD_DIR)/*.lst $(BUILD_DIR)/assets/
Expand Down
35 changes: 35 additions & 0 deletions examples/strip_unused_procs_asm_safe.has
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// strip_unused_procs_asm_safe.has
// Demonstrates the conservative asm-block safeguard in --strip-unused-procs.
//
// When a top-level raw asm block is present in a code section the compiler
// cannot safely prove which internal procedures are reachable via jsr/jmp
// in that block. The pass therefore keeps ALL internal procedures.
//
// Compile with stripping:
// python -m hasc.cli examples/strip_unused_procs_asm_safe.has \
// --strip-unused-procs --strip-unused-report -o /tmp/asm_safe.s
// → "top-level asm block detected; keeping all internal procedures"
// → labels: startup, helper, unreachable_from_has (all kept)

code demo:
// Top-level asm block that calls internal code via raw jsr.
// The compiler cannot statically trace these targets, so it keeps all procs.
asm {
jsr startup
rts
}

proc startup() -> void {
var r: int = helper(7);
return;
}

proc helper(x: int) -> int {
return x + 1;
}

// Called from raw asm above (not via HAS call statement).
// Without the asm-block safeguard this would be incorrectly removed.
proc unreachable_from_has() -> int {
return 0;
}
40 changes: 40 additions & 0 deletions examples/strip_unused_procs_demo.has
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// strip_unused_procs_demo.has
// Demonstrates --strip-unused-procs (dead internal procedure removal).
//
// Root: public entry_point;
// Reachable chain: entry_point → compute → helper
// Dead (never called): dead_a, dead_b
//
// Compile without stripping:
// python -m hasc.cli examples/strip_unused_procs_demo.has -o /tmp/demo.s
// → labels: entry_point, compute, helper, dead_a, dead_b
//
// Compile with stripping:
// python -m hasc.cli examples/strip_unused_procs_demo.has \
// --strip-unused-procs --strip-unused-report -o /tmp/demo.s
// → Strip report removed: dead_a, dead_b
// → labels: entry_point, compute, helper (dead procs absent)

code demo:
public entry_point;

proc entry_point() -> int {
return compute(10, 32);
}

proc compute(a: int, b: int) -> int {
return helper(a) + b;
}

proc helper(x: int) -> int {
return x * 2;
}

// These procedures are never called from any reachable path.
proc dead_a() -> int {
return 99;
}

proc dead_b() -> int {
return dead_a();
}
Loading