Skip to content

Commit fd9debd

Browse files
committed
update callback wrapper gen;cross-link opens
1 parent 3b07a8c commit fd9debd

13 files changed

Lines changed: 351 additions & 86 deletions

src/Farscape.Cli/Program.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ let pilotInitCommand =
319319
Callbacks = None
320320
Nonnull = None
321321
ProtocolConfig = None
322+
Layer3 = None
322323
}
323324

324325
let tomlPath = Path.Combine(outputDir, $"{library}.pilot.toml")

src/Farscape.Core/BindingGenerator.fs

Lines changed: 229 additions & 50 deletions
Large diffs are not rendered by default.

src/Farscape.Core/CallbackWrapperGenerator.fs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ module CallbackWrapperGenerator =
8585
let generateRegistrationWrapper
8686
(reg: CallbackRegistration)
8787
(funcDecl: CppParser.FunctionDecl)
88-
(bindingsModuleName: string)
8988
(model: Types.PlatformABI)
9089
: FsDecl list =
9190

@@ -106,8 +105,8 @@ module CallbackWrapperGenerator =
106105
// Body: let handler = dlsym 0n handlerSymbol in originalFunc arg1 handler arg2 ...
107106
let body =
108107
LetIn("handler",
109-
FunctionCall("Fidelity.Libc.DynamicLink", "dlsym", [Literal "0n"; Identifier "handlerSymbol"]),
110-
FunctionCall(bindingsModuleName, reg.Function,
108+
FunctionCall("", "dlsym", [Literal "0n"; Identifier "handlerSymbol"]),
109+
FunctionCall("", reg.Function,
111110
funcDecl.Parameters |> List.map (fun (name, _) ->
112111
if name = reg.CallbackParam then Identifier "handler"
113112
elif reg.DataParam = Some name then Literal "0n"
@@ -152,7 +151,7 @@ module CallbackWrapperGenerator =
152151
let fields =
153152
structDecl.Fields |> List.map (fun f ->
154153
if isCallbackField f then
155-
(f.Name, FunctionCall("Fidelity.Libc.DynamicLink", "dlsym",
154+
(f.Name, FunctionCall("", "dlsym",
156155
[Literal "0n"; Identifier (f.Name + "Sym")]))
157156
else
158157
// Non-callback field: zero-init
@@ -172,7 +171,6 @@ module CallbackWrapperGenerator =
172171
let generateDecls
173172
(spec: CallbackSpec)
174173
(declarations: CppParser.Declaration list)
175-
(bindingsModuleName: string)
176174
(model: Types.PlatformABI)
177175
: FsDecl list =
178176

@@ -191,7 +189,7 @@ module CallbackWrapperGenerator =
191189
| CppParser.Declaration.Function f when f.Name = reg.Function -> Some f
192190
| _ -> None)
193191
match funcDecl with
194-
| Some f -> generateRegistrationWrapper reg f bindingsModuleName model
192+
| Some f -> generateRegistrationWrapper reg f model
195193
| None -> [])
196194

197195
let listenerDecls =
@@ -215,12 +213,13 @@ module CallbackWrapperGenerator =
215213
(spec: CallbackSpec)
216214
(declarations: CppParser.Declaration list)
217215
(namespace': string)
218-
(bindingsModuleName: string)
219216
(model: Types.PlatformABI)
217+
(openModules: string list)
220218
: string option =
221219

222-
let decls = generateDecls spec declarations bindingsModuleName model
220+
let decls = generateDecls spec declarations model
223221
if decls.IsEmpty then None
224222
else
225-
let moduleDecl = Module(namespace', "Callback wrappers — dlsym-based runtime symbol resolution", decls)
223+
let opens = openModules |> List.map OpenModule
224+
let moduleDecl = Module(namespace', "Callback wrappers — dlsym-based runtime symbol resolution", opens @ decls)
226225
Some (CodeRenderer.render moduleDecl)

src/Farscape.Core/CodeRenderer.fs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ module CodeRenderer =
2424
let rec renderExpr (indent: string) = function
2525
| DefaultOf ty -> $"Unchecked.defaultof<{renderType ty}>"
2626
| FunctionCall (mod', name, args) ->
27-
let argStr = args |> List.map (renderExpr indent) |> String.concat " "
27+
let argStr = args |> List.map (renderArg indent) |> String.concat " "
2828
if mod' = "" then $"{name} {argStr}"
2929
else $"{mod'}.{name} {argStr}"
3030
| Identifier name -> name
@@ -57,6 +57,12 @@ module CodeRenderer =
5757
|> String.concat ""
5858
$"match {renderExpr indent scrutinee} with{caseStr}"
5959

60+
/// Render an expression as a function argument, parenthesizing compound expressions.
61+
and renderArg (indent: string) (expr: FsExpr) =
62+
match expr with
63+
| Identifier _ | Literal _ -> renderExpr indent expr
64+
| _ -> $"({renderExpr indent expr})"
65+
6066
/// Render a single FsDecl node to the StringBuilder at the given indentation level
6167
let rec private renderDecl (sb: StringBuilder) (indent: int) (decl: FsDecl) =
6268
let prefix = String.replicate indent " "

src/Farscape.Core/PilotAnalyzer.fs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,8 @@ module PilotAnalyzer =
393393
Options = None
394394
Callbacks = None
395395
Nonnull = None
396-
ProtocolConfig = None }
396+
ProtocolConfig = None
397+
Layer3 = None }
397398

398399
// =========================================================================
399400
// Declaration Filtering (for scoped generation)
@@ -568,3 +569,40 @@ module PilotAnalyzer =
568569
match declarationName decl with
569570
| Some name -> Set.contains name typeNames
570571
| None -> false)
572+
573+
// =========================================================================
574+
// Layer 3 Bridge Requirement Analysis
575+
// =========================================================================
576+
577+
/// Analyze whether a project needs a Layer 3 Bridge package.
578+
/// Examines protocol config and callbacks to determine cross-library dependencies.
579+
/// The unpairedConstructors parameter is computed by the caller from parsed protocol data
580+
/// (PilotAnalyzer cannot reference ProtocolParser due to fsproj ordering).
581+
let analyzeLayer3Requirements
582+
(project: PilotProject)
583+
(resolvedCallbackSpec: CallbackSpec option)
584+
(unpairedConstructors: string list)
585+
: Layer3Requirement option =
586+
587+
let hasProtocol = project.ProtocolConfig.IsSome
588+
let hasCallbacks =
589+
match resolvedCallbackSpec with
590+
| Some spec -> not spec.Registrations.IsEmpty || not spec.ListenerStructs.IsEmpty
591+
| None -> false
592+
593+
if not hasProtocol && not hasCallbacks then None
594+
else
595+
let deps =
596+
[ if hasProtocol then
597+
yield LibcDynamicLink
598+
yield LibcMemory
599+
if hasCallbacks then
600+
yield LibcDynamicLink ]
601+
|> List.distinct
602+
Some {
603+
Dependencies = deps
604+
HasProtocolDispatch = hasProtocol
605+
HasCallbackWrappers = hasCallbacks
606+
UnpairedConstructors = unpairedConstructors
607+
UnmappedPatterns = []
608+
}

src/Farscape.Core/PilotDiscovery.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,8 @@ module PilotDiscovery =
424424
Options = None
425425
Callbacks = None
426426
Nonnull = None
427-
ProtocolConfig = None }
427+
ProtocolConfig = None
428+
Layer3 = None }
428429

429430
// =========================================================================
430431
// IO Layer (CLI consumption)

src/Farscape.Core/PilotSerializer.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,8 @@ module PilotSerializer =
488488
Options = deserializeOptions doc
489489
Callbacks = deserializeCallbacks doc
490490
Nonnull = deserializeNonnull doc
491-
ProtocolConfig = deserializeProtocolConfig doc }
491+
ProtocolConfig = deserializeProtocolConfig doc
492+
Layer3 = None }
492493
| Error e, _, _ | _, Error e, _ | _, _, Error e -> Error e
493494

494495
// =========================================================================

src/Farscape.Core/PilotTypes.fs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,31 @@ module PilotTypes =
158158
DestroyFlag: uint32
159159
}
160160

161+
// =========================================================================
162+
// Layer 3 Bridge Requirements
163+
// =========================================================================
164+
165+
/// External dependencies required by Layer 3 bridge code.
166+
type Layer3Dependency =
167+
/// dlsym for interface resolution + callback binding
168+
| LibcDynamicLink
169+
/// malloc/free for protocol argument arrays
170+
| LibcMemory
171+
172+
/// Layer 3 requirement analysis result — determines whether a Bridge package is needed.
173+
type Layer3Requirement = {
174+
/// External dependencies the bridge code needs
175+
Dependencies: Layer3Dependency list
176+
/// Protocol dispatch implementations needed
177+
HasProtocolDispatch: bool
178+
/// Callback wrappers (dlsym-based) needed
179+
HasCallbackWrappers: bool
180+
/// Interfaces with constructors but no paired destructor — developer must review
181+
UnpairedConstructors: string list
182+
/// Patterns the generator couldn't handle — developer must implement by hand
183+
UnmappedPatterns: string list
184+
}
185+
161186
/// Complete Pilot project, corresponding to a .pilot.toml file.
162187
type PilotProject = {
163188
Library: LibrarySpec
@@ -173,6 +198,9 @@ module PilotTypes =
173198
Nonnull: NonnullAnnotations option
174199
/// Protocol dispatch configuration (None = no XML protocol request generation)
175200
ProtocolConfig: ProtocolConfig option
201+
/// Layer 3 bridge requirements (None = no bridge package needed).
202+
/// Computed by PilotAnalyzer.analyzeLayer3Requirements, not serialized.
203+
Layer3: Layer3Requirement option
176204
}
177205

178206
// =========================================================================

src/Farscape.Core/ProtocolParser.fs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -308,11 +308,11 @@ module ProtocolParser =
308308
| Int -> Named "int32"
309309
| Uint -> Named "uint32"
310310
| Fixed -> Named "int32" // wl_fixed_t is int32
311-
| String -> Generic("Option", Generic("nativeptr", Named "byte"))
312-
| Object -> Generic("Option", Named "nativeint")
313-
| NewId -> Generic("Option", Named "nativeint")
311+
| String -> Named "nativeint" // string pointer, passed as nativeint in arg array
312+
| Object -> Named "nativeint" // proxy handle
313+
| NewId -> Named "nativeint"
314314
| Fd -> Named "int32"
315-
| Array -> Generic("Option", Named "nativeint")
315+
| Array -> Named "nativeint" // wl_array pointer
316316

317317
/// Convert a protocol arg value to nativeint for the argument array.
318318
let private argToNativeint (arg: ProtocolArg) : FsExpr =
@@ -361,18 +361,18 @@ module ProtocolParser =
361361

362362
// Build the body
363363
let flags =
364-
if request.IsDestructor then Literal $"{config.DestroyFlag}u"
365-
else Literal "0u"
364+
if request.IsDestructor then TypeConversion("uint32", Literal $"{config.DestroyFlag}")
365+
else TypeConversion("uint32", Literal "0")
366366

367-
let opcodeExpr = Literal $"{opcode}u"
367+
let opcodeExpr = TypeConversion("uint32", Literal $"{opcode}")
368368

369369
// Interface pointer: resolve via dlsym for typed new_id, or use caller-provided for untyped
370370
let interfaceExpr =
371371
if isConstructor && not isUntypedNewId then
372372
match newIdArg with
373373
| Some a ->
374374
let targetIface = a.Interface |> Option.defaultValue iface.Name
375-
FunctionCall("Fidelity.Libc.DynamicLink", "dlsym", [Literal "0n"; Literal $"\"{targetIface}_interface\""])
375+
FunctionCall("", "dlsym", [Literal "0n"; Literal $"\"{targetIface}_interface\""])
376376
| None -> Literal "0n"
377377
elif isUntypedNewId then
378378
Identifier "``interface``"
@@ -384,7 +384,7 @@ module ProtocolParser =
384384
if isUntypedNewId then
385385
Identifier "version"
386386
else
387-
FunctionCall(config.MarshalModule, config.VersionFunction, [FunctionCall("", "Some", [Identifier "self"])])
387+
FunctionCall("", config.VersionFunction, [FunctionCall("", "Some", [Identifier "self"])])
388388

389389
// For requests with no args (besides self and new_id), pass None for args array
390390
// For requests with args, we need to construct the argument array
@@ -394,7 +394,7 @@ module ProtocolParser =
394394
if marshalArgs.IsEmpty && not isUntypedNewId then
395395
// Simple case: no argument array needed
396396
let marshalCall =
397-
FunctionCall(config.MarshalModule, config.MarshalFunction,
397+
FunctionCall("", config.MarshalFunction,
398398
[ FunctionCall("", "Some", [Identifier "self"])
399399
opcodeExpr
400400
FunctionCall("", "Some", [interfaceExpr])
@@ -416,7 +416,7 @@ module ProtocolParser =
416416
// This requires constructing an argument array with the bind-specific args
417417
// For now, generate with the regular args + interface name + version + NULL sentinel
418418
let marshalCall =
419-
FunctionCall(config.MarshalModule, config.MarshalFunction,
419+
FunctionCall("", config.MarshalFunction,
420420
[ FunctionCall("", "Some", [Identifier "self"])
421421
opcodeExpr
422422
FunctionCall("", "Some", [Identifier "``interface``"])
@@ -438,21 +438,21 @@ module ProtocolParser =
438438
let offset = i * 8
439439
// NativePtr.set on the buffer cast to nativeptr<nativeint>
440440
let writeExpr =
441-
FunctionCall("NativeInterop.NativePtr", "set",
441+
FunctionCall("NativePtr", "set",
442442
[ Identifier "argsPtr"; Literal $"{i}"; argToNativeint arg ])
443443
(i, writeExpr))
444444

445445
// Build the sequential expression: alloc, write args, marshal, free
446446
let allocExpr =
447-
FunctionCall("Fidelity.Libc.Memory", "malloc",
447+
FunctionCall("", "malloc",
448448
[ TypeConversion("unativeint", Literal allocSize) ])
449449
let castExpr =
450-
FunctionCall("NativeInterop.NativePtr", "ofNativeInt",
450+
FunctionCall("NativePtr", "ofNativeInt",
451451
[ Identifier "argsRaw" ])
452452

453453
// Chain: let argsRaw = malloc(...) in let argsPtr = cast in write0; write1; ... marshal; free
454454
let marshalCall =
455-
FunctionCall(config.MarshalModule, config.MarshalFunction,
455+
FunctionCall("", config.MarshalFunction,
456456
[ FunctionCall("", "Some", [Identifier "self"])
457457
opcodeExpr
458458
FunctionCall("", "Some", [interfaceExpr])
@@ -461,7 +461,7 @@ module ProtocolParser =
461461
FunctionCall("", "Some", [Identifier "argsRaw"]) ])
462462

463463
let freeCall =
464-
FunctionCall("Fidelity.Libc.Memory", "free",
464+
FunctionCall("", "free",
465465
[ FunctionCall("", "Some", [Identifier "argsRaw"]) ])
466466

467467
// Build nested let expressions for arg writes, then marshal + free

tests/Farscape.Tests/CallbackTests.fs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ module CallbackSerializerTests =
226226
}
227227
Nonnull = None
228228
ProtocolConfig = None
229+
Layer3 = None
229230
}
230231
let toml = PilotSerializer.toTomlString project
231232
Assert.Contains("callbacks", toml)
@@ -264,6 +265,7 @@ module CallbackSerializerTests =
264265
}
265266
Nonnull = None
266267
ProtocolConfig = None
268+
Layer3 = None
267269
}
268270
let toml = PilotSerializer.toTomlString project
269271
Assert.Contains("listener_structs", toml)
@@ -308,6 +310,7 @@ module CallbackSerializerTests =
308310
}
309311
Nonnull = None
310312
ProtocolConfig = None
313+
Layer3 = None
311314
}
312315
let toml = PilotSerializer.toTomlString project
313316
match Fidelity.Data.TOML.Toml.parse toml with
@@ -345,7 +348,7 @@ module CallbackWrapperGeneratorTests =
345348
mkField "motion" "void (*)(void *, struct wl_pointer *, uint32_t, wl_fixed_t, wl_fixed_t)"
346349
] None)
347350
]
348-
match CallbackWrapperGenerator.generate spec decls "Fidelity.Wayland.Callbacks" "Fidelity.Wayland" LP64 with
351+
match CallbackWrapperGenerator.generate spec decls "Fidelity.Wayland.Callbacks" LP64 [] with
349352
| None -> Assert.Fail "Expected some generated output"
350353
| Some code ->
351354
Assert.Contains("dlsym", code)
@@ -373,7 +376,7 @@ module CallbackWrapperGeneratorTests =
373376
mkField "leave" "WlPointerLeaveHandler"
374377
] None)
375378
]
376-
match CallbackWrapperGenerator.generate spec decls "Fidelity.Wayland.Callbacks" "Fidelity.Wayland" LP64 with
379+
match CallbackWrapperGenerator.generate spec decls "Fidelity.Wayland.Callbacks" LP64 [] with
377380
| None -> Assert.Fail "Expected some generated output"
378381
| Some code ->
379382
Assert.Contains("dlsym", code)
@@ -390,13 +393,13 @@ module CallbackWrapperGeneratorTests =
390393
]
391394
}
392395
// No struct found → no decls generated → None
393-
let result = CallbackWrapperGenerator.generate spec [] "Fidelity.Test.Callbacks" "Fidelity.Test" LP64
396+
let result = CallbackWrapperGenerator.generate spec [] "Fidelity.Test.Callbacks" LP64 []
394397
Assert.True(result.IsNone)
395398

396399
[<Fact>]
397400
let ``empty callback spec produces None`` () =
398401
let spec : CallbackSpec = { Registrations = []; ListenerStructs = [] }
399-
let result = CallbackWrapperGenerator.generate spec [] "Ns" "Mod" LP64
402+
let result = CallbackWrapperGenerator.generate spec [] "Ns" LP64 []
400403
Assert.True(result.IsNone)
401404

402405
[<Fact>]
@@ -412,7 +415,7 @@ module CallbackWrapperGeneratorTests =
412415
mkFunc "g_idle_add" "unsigned int"
413416
[("function", "int (*)(void *)"); ("data", "void *")])
414417
]
415-
match CallbackWrapperGenerator.generate spec decls "Fidelity.GTK.Callbacks" "Fidelity.GTK" LP64 with
418+
match CallbackWrapperGenerator.generate spec decls "Fidelity.GTK.Callbacks" LP64 [] with
416419
| None -> Assert.Fail "Expected some output"
417420
| Some code ->
418421
Assert.Contains("dlsym", code)
@@ -434,7 +437,7 @@ module CallbackWrapperGeneratorTests =
434437
mkFunc "signal" "void (*)(int)"
435438
[("signum", "int"); ("handler", "void (*)(int)")])
436439
]
437-
match CallbackWrapperGenerator.generate spec decls "Fidelity.Libc.Callbacks" "Fidelity.Libc" LP64 with
440+
match CallbackWrapperGenerator.generate spec decls "Fidelity.Libc.Callbacks" LP64 [] with
438441
| None -> Assert.Fail "Expected some output"
439442
| Some code ->
440443
Assert.Contains("signum", code)
@@ -449,5 +452,5 @@ module CallbackWrapperGeneratorTests =
449452
]
450453
ListenerStructs = []
451454
}
452-
let result = CallbackWrapperGenerator.generate spec [] "Ns" "Mod" LP64
455+
let result = CallbackWrapperGenerator.generate spec [] "Ns" LP64 []
453456
Assert.True(result.IsNone)

0 commit comments

Comments
 (0)