FStarLang / FStarLang/FStar

Pulse.Lib.SpinLock C Extraction: Calling Convention Mismatch

Open
#4,151 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
F*
Stars
3.1k
Forks
266
Avg merge
21h 1m
Merged PRs (30d)
54

Description

## Summary

KaRaMeL generates calls to `acquire`/`release` with erased ghost
arguments that don't match the signatures in the shipped
`Pulse_Lib_SpinLock.h`. Users must write wrapper functions to bridge
the gap.

## Generated C (actual)

```c
Pulse_Lib_SpinLock_acquire((void *)0U, (void *)0U, lk);
Pulse_Lib_SpinLock_release((void *)0U, (void *)0U, lk);
```

## Shipped header (`dice/external/c/hacl/Pulse_Lib_SpinLock.h`)

```c
void Pulse_Lib_SpinLock_acquire(Pulse_Lib_SpinLock_lock l); // 1 arg
void Pulse_Lib_SpinLock_release(Pulse_Lib_SpinLock_lock l); // 1 arg
```

The two extra `(void *)0U` arguments are erased ghost parameters
(`#v:slprop` and `#p:perm`) that KaRaMeL doesn't fully erase.

## Minimal reproducer

### `spinlock-repro.fst`

```fstar
module SpinlockRepro

open Pulse.Lib.Pervasives
module L = Pulse.Lib.SpinLock
module B = Pulse.Lib.Box

#lang-pulse
fn protected_write (r: B.box int) (lk: L.lock)
requires L.lock_alive lk #1.0R (exists* v. B.pts_to r v)
ensures L.lock_alive lk #1.0R (exists* v. B.pts_to r v)
{
L.acquire lk;
B.op_Colon_Equals r 42;
L.release lk
}

#lang-pulse
fn main_fn ()
requires emp
ensures emp
{
let r = B.alloc #int 0;
let lk = L.new_lock (exists* v. B.pts_to r v);
protected_write r lk;
drop_ (L.lock_alive lk #1.0R (exists* v. B.pts_to r v))
}
```

### Steps to reproduce

```bash
# 1. Verify + extract to .krml
pulse --codegen krml --odir _build \
--extract 'krml:SpinlockRepro' spinlock-repro.fst

# 2. Run KaRaMeL
krml -skip-compilation -warn-error -2 \
-library Pulse.Lib.SpinLock \
-add-include '"Pulse_Lib_SpinLock.h"' \
-tmpdir _build \
$KARAMEL_HOME/krmllib/.extract/FStar_Pervasives_Native.krml \
$KARAMEL_HOME/krmllib/.extract/FStar_Pervasives.krml \
_build/SpinlockRepro.krml

# 3. Inspect generated C
cat _build/SpinlockRepro.c
```

### Full generated output

```c
void SpinlockRepro_protected_write(krml_checked_int_t *r,
Pulse_Lib_SpinLock_lock lk)
{
Pulse_Lib_SpinLock_acquire((void *)0U, (void *)0U, lk);
*r = (krml_checked_int_t)42;
Pulse_Lib_SpinLock_release((void *)0U, (void *)0U, lk);
}

void SpinlockRepro_main_fn(void)
{
krml_checked_int_t *r = KRML_HOST_CALLOC(1U, sizeof (krml_checked_int_t));
Pulse_Lib_SpinLock_lock lk = Pulse_Lib_SpinLock_new_lock();
SpinlockRepro_protected_write(r, lk);
}
```

Note that `new_lock()` is fine (0 args) but `acquire` and `release`
get 2 spurious `(void *)0U` arguments.

## Current workaround

Write a wrapper header that accepts and discards the ghost arguments:

```c
static inline void
Pulse_Lib_SpinLock_acquire(void *g1, void *g2,
Pulse_Lib_SpinLock_lock l) {
(void)g1; (void)g2;
Pulse_Lib_SpinLock_acquire_impl(l);
}
```

This shouldn't be necessary -- the shipped `.h`/`.c` should match
what KaRaMeL generates, or KaRaMeL should fully erase the ghost
parameters.

## Environment

- Pulse/F\*: 2025.12.15\~dev
- KaRaMeL: d7a8f9ec
- The shipped `Pulse_Lib_SpinLock.{h,c}` are at
`$PULSE_HOME/share/pulse/examples/dice/external/c/hacl/`

Contributor guide

Open the contributing guide

Research direction

Start with the spinlock-repro.fst reproducer and run the listed pulse and krml commands to inspect the generated SpinlockRepro.c. Compare the acquire/release calls with dice/external/c/hacl/Pulse_Lib_SpinLock.h and its companion .c file. Done means the generated calls match the shipped one-argument declarations without wrapper functions.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.