Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
875413f
Refactor and commonize compiler.py, create symbols.py
zimri-leisher Jun 8, 2026
e6ff92b
Fix imports
zimri-leisher Jun 8, 2026
2a1c2e5
Continue to fix import errors
zimri-leisher Jun 8, 2026
8e5782d
Fix all tests
zimri-leisher Jun 8, 2026
58ff2a6
Map Fpy types to llvm types
zimri-leisher Jun 8, 2026
65d3a59
Variable declaration and access
zimri-leisher Jun 9, 2026
88d92c2
Add flags variable and wasm test arg
zimri-leisher Jun 9, 2026
7c56d4d
Exit macro
zimri-leisher Jun 9, 2026
b4c8569
If statements, globals
zimri-leisher Jun 9, 2026
c2b565d
Store llvm_ptr in var sym
zimri-leisher Jun 9, 2026
44e217b
Finish off arithmetic ops, make floor div and modulo consistent
zimri-leisher Jun 9, 2026
c191195
Add --emit wat option
zimri-leisher Jun 10, 2026
520819f
Merge branch 'devel' of github.com:fprime-community/fpy into 56-llvm-…
zimri-leisher Jun 10, 2026
0f28a39
Fix dict test
zimri-leisher Jun 10, 2026
867ba8c
Cast float to int saturating
zimri-leisher Jun 11, 2026
66883b3
Finish off arithmetic funcs, p much all arithmetic tests pass
zimri-leisher Jun 11, 2026
6f253e9
Fix all assert tests
zimri-leisher Jun 11, 2026
2a23f63
Merge branch 'devel' of github.com:fprime-community/fpy into 56-llvm-…
zimri-leisher Jun 14, 2026
b36a6d4
ModuleSymbol
zimri-leisher Jun 14, 2026
43a07b6
Add spacewasm submodule
zimri-leisher Jun 28, 2026
09c4750
Add a uv lock, remove support for python 3.9
zimri-leisher Jun 28, 2026
5296c76
Refactor llvm backend a bit, handle no op exprs on a line alone corre…
zimri-leisher Jun 29, 2026
badddd3
Add iabs/fabs directives
zimri-leisher Jun 30, 2026
3445d4b
Separate error code and trap
zimri-leisher Jun 30, 2026
f8fc208
Add error msgs to time funcs
zimri-leisher Jun 30, 2026
ac24cca
Skip wasm tests unless --wasm is passed
zimri-leisher Jun 30, 2026
f1eeb9a
Run WASM tests on WASM always
zimri-leisher Jun 30, 2026
48c72f7
Merge branch 'devel' of github.com:fprime-community/fpy into 56-llvm-…
zimri-leisher Jun 30, 2026
28c417f
Host exit func
zimri-leisher Jun 30, 2026
536226e
Remove 3.9 support from actions
zimri-leisher Jun 30, 2026
bc9ec48
sp
zimri-leisher Jun 30, 2026
22c25c0
actions init spacewasm
zimri-leisher Jun 30, 2026
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
45 changes: 42 additions & 3 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
addinivalue
addoption
alloca
asmprinters
astassign
autouse
BBBBBHI
binop
bitwidth
Breslav
brk
calcsize
capsys
cbranch
Ccsds
Cdh
chans
Expand All @@ -20,29 +25,38 @@ downcasted
downcasting
elifs
ev
fcmp
fd
feq
ffloor
fge
fgt
fle
fors
fne
fneg
fors
fpext
fpp
fpow
fpp
fprime
fptoint
fptosi
fptoui
fptrunc
fpy
fpyasm
fpybc
fpybin
fqn
frem
fsw
getattrs
getcontext
getfixturevalue
getoption
github
globalopt
gvar
gz
hh
HLTH
Expand All @@ -52,13 +66,26 @@ ieq
ifelifelse
IH
ine
intrinsics
ip
itrunc
kinda
lalr
libcall
libm
lvar
miscompiling
mvp
mx
nassert
nbackend
nexit
nif
nontrapping
noreturn
nsize
oob
oor
pb
pemdas
postlex
Expand All @@ -68,9 +95,11 @@ PRNG
probs
px
py
pytestmark
pyversions
randf
rb
rustup
SADMEP
sdiv
sge
Expand All @@ -82,6 +111,9 @@ skipif
sle
smod
smth
spacewasm
srem
sroa
subcls
sus
tcp
Expand All @@ -94,16 +126,23 @@ uitofp
uk
ul
umod
urem
usec
useconds
usize
vararg
vec
verflow
vh
vw
WASI
wasmtime
wikipedia
wrt
wx
xl
xy
zext
ziext
zmq
ziglang
zmq
3 changes: 2 additions & 1 deletion .github/workflows/fprime-gds-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
Expand All @@ -29,6 +29,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -e .
git submodule update --init test/spacewasm
- name: Test with pytest
run: |
pytest
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,9 @@ dmypy.json

# Cython debug symbols
cython_debug/
.claude/settings.local.json
*.bin
*.fpy
*.ll
*.wasm
*.wat
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "test/spacewasm"]
path = test/spacewasm
url = https://github.com/nasa/spacewasm
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
Guidelines:
* If you're going to run Python, use `python3`
* Use the venv in the folder named `venv`
* Use pytest to run tests.
* This project uses `uv`. Dependencies are declared in `pyproject.toml` and locked in `uv.lock`.
* Use `uv sync` to create/update the `.venv` and install dependencies.
* Run commands in the project environment with `uv run` (e.g. run Python with `uv run python3`).
* Run tests with `uv run pytest`.
* If no tests are found, that usually means that there is an import error in the test files
* If something should never happen, assert. Don't silently return or use defensive `if x is None: return` guards for cases that represent bugs or invariant violations—crash instead so bugs surface immediately.
* Compile errors in semantics passes should always return ASAP. No continuing on if an error has been discovered.
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "fprime-fpy"
dynamic = ["version"]
description = "Fpy Advanced Sequencing Language for F Prime"
readme = "README.md"
requires-python = ">=3.9"
requires-python = ">=3.10"
license = {file = "LICENSE.txt"}
keywords = ["fprime", "embedded", "nasa", "flight", "software"]
authors = [
Expand All @@ -21,7 +21,6 @@ classifiers = [
"Operating System :: POSIX",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand All @@ -33,6 +32,11 @@ classifiers = [
dependencies = [
"pytest>=6.2.4",
"lark>=1.2.2",
"llvmlite>=0.44.0",
# Provides wasm-ld (LLVM's linker) to link the LLVM/wasm backend's object output.
"ziglang>=0.16.0",
# Interpreted wasm runtime used to execute the LLVM/wasm backend's output.
"wasmtime>=45.0.0",
]

[project.urls]
Expand Down
6 changes: 3 additions & 3 deletions src/fpy/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ Inline macros behave like functions whose bodies are pre-defined sequences of by

Available macros:

* `exit(exit_code: U8)`: terminates the sequence immediately by emitting an `ExitDirective`.
* `exit(exit_code: I32)`: terminates the sequence immediately by emitting an `ExitDirective`.
* `ln(operand: F64) -> F64`: computes the natural logarithm of the operand using `FloatLogDirective` and leaves the `F64` result on the stack.
* `sleep(seconds: U32 = 0, microseconds: U32 = 0)`: waits for the specified relative duration (the assembler emits `WaitRelDirective`).
* `sleep_until(wakeup_time: Fw.TimeValue)`: waits until the supplied absolute time using `WaitAbsDirective`.
Expand All @@ -126,8 +126,8 @@ Available macros:
* `randf() -> F64`: pushes the next random number via `PushRandDirective`, converts it to `F64`, and divides by `2**32` to produce a value in the half-open range `[0.0, 1.0)`.
* `set_seed(seed: U32)`: seeds the random number generator via `SetSeedDirective`.
* `time(timestamp: String, timeBase: TimeBase = TimeBase.TB_NONE, timeContext: U8 = 0) -> Fw.TimeValue`: parses an ISO 8601 timestamp string (e.g., `"2025-12-19T14:30:00Z"` or `"2025-12-19T14:30:00.123456Z"`) at compile time and returns an `Fw.TimeValue` with the specified `timeBase` and `timeContext`. The timestamp must be in UTC with a `Z` suffix.
* `iabs(value: I64) -> I64`: returns the absolute value of a signed 64-bit integer.
* `fabs(value: F64) -> F64`: returns the absolute value of a 64-bit float.
* `iabs(value: I64) -> I64`: returns the absolute value of a signed 64-bit integer by emitting an `IntAbsDirective`. The absolute value of `I64` min (`-2**63`) wraps back to `I64` min rather than trapping, matching libm's `llabs`.
* `fabs(value: F64) -> F64`: returns the absolute value of a 64-bit float by emitting a `FloatAbsDirective`.
* `log(message: String, severity: Fw.LogSeverity = Fw.LogSeverity.ACTIVITY_HI)`: pushes the severity, message, and message size onto the stack, then emits a `PopEventDirective`. Both arguments must be compile-time constants.

## Type constructors
Expand Down
63 changes: 41 additions & 22 deletions src/fpy/builtin/time.fpy
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ def time_cmp(lhs: Fw.Time, rhs: Fw.Time) -> Fw.TimeComparison:
def time_cmp_assert_comparable(lhs: Fw.Time, rhs: Fw.Time) -> Fw.TimeComparison:
# Like time_cmp but asserts that times have the same timeBase.
# Used by comparison operators (< > <= >= == !=) to ensure comparable times.
assert lhs.timeBase == rhs.timeBase, 1
if lhs.timeBase != rhs.timeBase:
log("time_cmp_assert_comparable: operands have different time bases", Fw.LogSeverity.WARNING_HI)
assert False, 1
lhs_micros: U64 = lhs.seconds * 1_000_000 + lhs.useconds
rhs_micros: U64 = rhs.seconds * 1_000_000 + rhs.useconds
if lhs_micros < rhs_micros:
Expand All @@ -34,19 +36,24 @@ def time_interval_cmp(lhs: Fw.TimeIntervalValue, rhs: Fw.TimeIntervalValue) -> F

# time subtraction produces a time delta
def time_sub(lhs: Fw.Time, rhs: Fw.Time) -> Fw.TimeIntervalValue:
# print time base incomparable?
assert lhs.timeBase == rhs.timeBase, 1
if lhs.timeBase != rhs.timeBase:
log("time_sub: operands have different time bases", Fw.LogSeverity.WARNING_HI)
assert False, 1
lhs_micros: U64 = lhs.seconds * 1_000_000 + lhs.useconds
rhs_micros: U64 = rhs.seconds * 1_000_000 + rhs.useconds

# check underflow
assert lhs_micros >= rhs_micros, 1
if lhs_micros < rhs_micros:
log("time_sub: subtraction would underflow", Fw.LogSeverity.WARNING_HI)
assert False, 1
result_micros: U64 = lhs_micros - rhs_micros

# Result seconds is guaranteed to fit in U32 since result <= lhs which came from U32,
# but we check explicitly for safety
result_seconds: U64 = result_micros // 1_000_000
assert result_seconds <= 0xFFFF_FFFF, 1
if result_seconds > 0xFFFF_FFFF:
log("time_sub: result seconds exceed U32", Fw.LogSeverity.WARNING_HI)
assert False, 1

return Fw.TimeIntervalValue(U32(result_seconds), U32(result_micros % 1_000_000))

Expand All @@ -55,46 +62,58 @@ def time_sub(lhs: Fw.Time, rhs: Fw.Time) -> Fw.TimeIntervalValue:
def time_add(lhs: Fw.Time, rhs: Fw.TimeIntervalValue) -> Fw.Time:
lhs_micros: U64 = lhs.seconds * 1_000_000 + lhs.useconds
rhs_micros: U64 = rhs.seconds * 1_000_000 + rhs.useconds

# check overflow: max U64 value minus lhs should leave enough space for rhs
assert 0xFFFF_FFFF_FFFF_FFFF - lhs_micros >= rhs_micros, 1
if 0xFFFF_FFFF_FFFF_FFFF - lhs_micros < rhs_micros:
log("time_add: addition would overflow", Fw.LogSeverity.WARNING_HI)
assert False, 1
result_micros: U64 = lhs_micros + rhs_micros

# check that result seconds fits in U32
result_seconds: U64 = result_micros // 1_000_000
assert result_seconds <= 0xFFFF_FFFF, 1

if result_seconds > 0xFFFF_FFFF:
log("time_add: result seconds exceed U32", Fw.LogSeverity.WARNING_HI)
assert False, 1

return Fw.Time(lhs.timeBase, lhs.timeContext, U32(result_seconds), U32(result_micros % 1_000_000))


# time interval addition
def time_interval_add(lhs: Fw.TimeIntervalValue, rhs: Fw.TimeIntervalValue) -> Fw.TimeIntervalValue:
lhs_micros: U64 = lhs.seconds * 1_000_000 + lhs.useconds
rhs_micros: U64 = rhs.seconds * 1_000_000 + rhs.useconds

# check overflow
assert 0xFFFF_FFFF_FFFF_FFFF - lhs_micros >= rhs_micros, 1
if 0xFFFF_FFFF_FFFF_FFFF - lhs_micros < rhs_micros:
log("time_interval_add: addition would overflow", Fw.LogSeverity.WARNING_HI)
assert False, 1
result_micros: U64 = lhs_micros + rhs_micros

# check that result seconds fits in U32
result_seconds: U64 = result_micros // 1_000_000
assert result_seconds <= 0xFFFF_FFFF, 1

if result_seconds > 0xFFFF_FFFF:
log("time_interval_add: result seconds exceed U32", Fw.LogSeverity.WARNING_HI)
assert False, 1

return Fw.TimeIntervalValue(U32(result_seconds), U32(result_micros % 1_000_000))


# time interval subtraction
def time_interval_sub(lhs: Fw.TimeIntervalValue, rhs: Fw.TimeIntervalValue) -> Fw.TimeIntervalValue:
lhs_micros: U64 = lhs.seconds * 1_000_000 + lhs.useconds
rhs_micros: U64 = rhs.seconds * 1_000_000 + rhs.useconds

# check underflow
assert lhs_micros >= rhs_micros, 1
if lhs_micros < rhs_micros:
log("time_interval_sub: subtraction would underflow", Fw.LogSeverity.WARNING_HI)
assert False, 1
result_micros: U64 = lhs_micros - rhs_micros

# Result seconds is guaranteed to fit in U32 since result <= lhs which came from U32,
# but we check explicitly for safety
result_seconds: U64 = result_micros // 1_000_000
assert result_seconds <= 0xFFFF_FFFF, 1

return Fw.TimeIntervalValue(U32(result_seconds), U32(result_micros % 1_000_000))
if result_seconds > 0xFFFF_FFFF:
log("time_interval_sub: result seconds exceed U32", Fw.LogSeverity.WARNING_HI)
assert False, 1

return Fw.TimeIntervalValue(U32(result_seconds), U32(result_micros % 1_000_000))
39 changes: 39 additions & 0 deletions src/fpy/bytecode/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -1136,3 +1136,42 @@ If this is called without the seed being manually set beforehand, then the seed
| ------------------|-------------|
| U32 | The next pseudorandom 32-bit value from the sequencer's internal PRNG |

## POP_SERIALIZABLE (78)
Reserved for a future directive that pops a serializable value off the stack. Not yet implemented — the opcode is reserved so its id stays stable for future use.

## FFLOOR (79)
Floors a float toward negative infinity, pushes result to stack. Infinity and NaN values pass through unchanged, consistent with wasm's `f64.floor`. Used to lower float floor division (`//`).
| Arg Name | Arg Type | Source | Description |
|----------|----------|--------|-------------|
| value | F64 | stack | Value to floor |

| Stack Result Type | Description |
| ------------------|-------------|
| F64 | The floored value |

**Requirement:** FPY-SEQ-002

## IABS (80)
Pops a signed `I64`, pushes its absolute value to the stack. The absolute value of `I64` min (`-2**63`) wraps back to `I64` min rather than trapping, matching libm's `llabs` and LLVM's `llvm.abs`.
| Arg Name | Arg Type | Source | Description |
|----------|----------|--------|-------------|
| value | I64 | stack | Value to take the absolute value of |

| Stack Result Type | Description |
| ------------------|-------------|
| I64 | The absolute value |

**Requirement:** FPY-SEQ-002

## FABS (81)
Pops an `F64`, pushes its absolute value to the stack, consistent with `llvm.fabs`. The sign bit is cleared, so negative zero becomes positive zero and NaN/infinity magnitudes pass through.
| Arg Name | Arg Type | Source | Description |
|----------|----------|--------|-------------|
| value | F64 | stack | Value to take the absolute value of |

| Stack Result Type | Description |
| ------------------|-------------|
| F64 | The absolute value |

**Requirement:** FPY-SEQ-002

Loading
Loading