Skip to content

Commit 4f99c21

Browse files
committed
Introducing architecture document
1 parent 98efb49 commit 4f99c21

File tree

2 files changed

+383
-0
lines changed

2 files changed

+383
-0
lines changed

ARCHITECTURE.md

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
# Swift-Java Architecture Guide
2+
3+
This guide explains the architecture and data flow of swift-java's jextract tool to help contributors understand where to make changes.
4+
5+
## Overview
6+
7+
Swift-Java provides bidirectional interoperability between Swift and Java. The main tool, `jextract`, generates bindings that allow Java code to call Swift APIs.
8+
9+
```
10+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
11+
│ Swift Source │ │ Intermediate │ │ Generated Code │
12+
│ (.swift) │ ───▶ │ Representation │ ───▶ │ (.java, .swift) │
13+
└─────────────────┘ └─────────────────┘ └─────────────────┘
14+
Parse ImportedDecls Generator (JNI/FFM)
15+
```
16+
17+
## Processing Pipeline
18+
19+
### 1. Entry Point: `Swift2Java`
20+
21+
**File:** `Sources/JExtractSwiftLib/Swift2Java.swift`
22+
23+
The main entry point that orchestrates the entire process:
24+
25+
```swift
26+
public func run() throws {
27+
let translator = Swift2JavaTranslator(config: config)
28+
29+
// 1. Register Swift source files
30+
for file in allFiles {
31+
translator.add(filePath: file.path, text: text)
32+
}
33+
34+
// 2. Analyze and build intermediate representation
35+
try translator.analyze()
36+
37+
// 3. Generate output based on mode (JNI or FFM)
38+
switch config.effectiveMode {
39+
case .jni:
40+
let generator = JNISwift2JavaGenerator(...)
41+
try generator.generate()
42+
case .ffm:
43+
let generator = FFMSwift2JavaGenerator(...)
44+
try generator.generate()
45+
}
46+
}
47+
```
48+
49+
### 2. Translation: `Swift2JavaTranslator`
50+
51+
**File:** `Sources/JExtractSwiftLib/Swift2JavaTranslator.swift`
52+
53+
Parses Swift source files and builds a symbol table:
54+
55+
- Uses SwiftSyntax to parse `.swift` files
56+
- Builds `SwiftSymbolTable` for type resolution
57+
- Runs `Swift2JavaVisitor` to extract declarations
58+
59+
**Key outputs:**
60+
- `importedTypes`: Map of Swift type names to `ImportedNominalType`
61+
- `importedGlobalFuncs`: List of global functions
62+
- `importedGlobalVariables`: List of global variables
63+
64+
### 3. Visitor: `Swift2JavaVisitor`
65+
66+
**File:** `Sources/JExtractSwiftLib/Swift2JavaVisitor.swift`
67+
68+
Walks the Swift AST and creates `ImportedDecl` objects:
69+
70+
```swift
71+
func visit(decl node: DeclSyntax, ...) {
72+
switch node.as(DeclSyntaxEnum.self) {
73+
case .classDecl(let node):
74+
self.visit(nominalDecl: node, ...)
75+
case .functionDecl(let node):
76+
self.visit(functionDecl: node, ...)
77+
// ... handles all declaration types
78+
}
79+
}
80+
```
81+
82+
**What gets extracted:**
83+
- Classes, structs, enums, protocols, actors
84+
- Methods, initializers, properties
85+
- Global functions and variables
86+
- Enum cases with associated values
87+
88+
### 4. Intermediate Representation: `ImportedDecls`
89+
90+
**File:** `Sources/JExtractSwiftLib/ImportedDecls.swift`
91+
92+
The bridge between parsing and code generation:
93+
94+
```
95+
ImportedNominalType (class/struct/enum)
96+
├── initializers: [ImportedFunc]
97+
├── methods: [ImportedFunc]
98+
├── variables: [ImportedFunc] (getters/setters)
99+
└── cases: [ImportedEnumCase]
100+
101+
ImportedFunc
102+
├── name: String
103+
├── functionSignature: SwiftFunctionSignature
104+
├── swiftDecl: DeclSyntax
105+
└── apiKind: SwiftAPIKind (.function, .initializer, .getter, .setter)
106+
```
107+
108+
### 5. Type System: `SwiftTypes/`
109+
110+
**Directory:** `Sources/JExtractSwiftLib/SwiftTypes/`
111+
112+
Represents Swift's type system in a structured way:
113+
114+
| File | Purpose |
115+
|------|---------|
116+
| `SwiftType.swift` | Core type representation (nominal, function, tuple, optional, etc.) |
117+
| `SwiftFunctionType.swift` | Function types with parameters, result, effects |
118+
| `SwiftFunctionSignature.swift` | Full function signature with labels |
119+
| `SwiftParameter.swift` | Parameter with type, label, attributes |
120+
| `SwiftNominalTypeDeclaration.swift` | Class/struct/enum declarations |
121+
| `SwiftKnownTypes.swift` | Built-in types (Int, String, etc.) |
122+
123+
**Example:** When you see `@escaping () -> Void`:
124+
125+
```swift
126+
SwiftType.function(SwiftFunctionType(
127+
parameters: [],
128+
resultType: .tuple([]), // Void
129+
isEscaping: true
130+
))
131+
```
132+
133+
### 6. Code Generators
134+
135+
Two backend generators convert `ImportedDecls` to output code:
136+
137+
#### JNI Generator
138+
139+
**Directory:** `Sources/JExtractSwiftLib/JNI/`
140+
141+
| File | Purpose |
142+
|------|---------|
143+
| `JNISwift2JavaGenerator.swift` | Main generator class |
144+
| `JNISwift2JavaGenerator+JavaBindingsPrinting.swift` | Generates Java wrapper classes |
145+
| `JNISwift2JavaGenerator+SwiftThunkPrinting.swift` | Generates Swift `@_cdecl` thunks |
146+
| `JNISwift2JavaGenerator+NativeTranslation.swift` | Type conversion Swift↔JNI |
147+
| `JNISwift2JavaGenerator+JavaTranslation.swift` | Type conversion Java↔JNI |
148+
149+
**Output:**
150+
- `.java` files with `native` methods
151+
- `.swift` files with `@_cdecl` functions
152+
153+
#### FFM Generator
154+
155+
**Directory:** `Sources/JExtractSwiftLib/FFM/`
156+
157+
Uses Java's Foreign Function & Memory API (JEP 454).
158+
159+
| File | Purpose |
160+
|------|---------|
161+
| `FFMSwift2JavaGenerator.swift` | Main generator class |
162+
| `FFMSwift2JavaGenerator+FunctionLowering.swift` | Lowers Swift types to C types |
163+
| `FFMSwift2JavaGenerator+JavaBindingsPrinting.swift` | Generates Java with MethodHandles |
164+
| `FFMSwift2JavaGenerator+SwiftThunkPrinting.swift` | Generates Swift `@_cdecl` thunks |
165+
166+
## Adding a New Feature: Decision Tree
167+
168+
```
169+
Want to support a new Swift feature?
170+
171+
├─▶ Is it a new type construct?
172+
│ └─▶ Modify: SwiftTypes/ (parsing)
173+
│ Then: JNI/ or FFM/ (generation)
174+
175+
├─▶ Is it a new declaration kind?
176+
│ └─▶ Modify: Swift2JavaVisitor.swift (extraction)
177+
│ Add: New ImportedDecl type if needed
178+
│ Then: Generator files (output)
179+
180+
├─▶ Is it modifying how existing types convert?
181+
│ └─▶ Modify: *+NativeTranslation.swift (Swift↔JNI)
182+
│ Or: *+JavaTranslation.swift (Java side)
183+
184+
└─▶ Is it changing generated code structure?
185+
└─▶ Modify: *+JavaBindingsPrinting.swift (Java output)
186+
Or: *+SwiftThunkPrinting.swift (Swift output)
187+
```
188+
189+
## Key Files by Task
190+
191+
### "I want to parse a new Swift attribute/type"
192+
193+
1. `SwiftTypes/SwiftType.swift` - Add new case to `SwiftType` enum
194+
2. `SwiftTypes/SwiftFunctionType.swift` - If it's function-related
195+
3. `Swift2JavaVisitor.swift` - Extract the attribute during visiting
196+
197+
### "I want to change how types are converted for JNI"
198+
199+
1. `JNI/JNISwift2JavaGenerator+NativeTranslation.swift`
200+
- `translateParameter()` - How Swift params become JNI
201+
- `translateResult()` - How results are converted back
202+
- `NativeSwiftConversionStep.render()` - Code generation for conversions
203+
204+
### "I want to change the generated Java code"
205+
206+
1. `JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift`
207+
- `printJavaBindingWrapperClass()` - Class structure
208+
- `printJavaBindingWrapperMethod()` - Method signatures
209+
210+
### "I want to change the generated Swift thunks"
211+
212+
1. `JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift`
213+
- `printCDecl()` - The `@_cdecl` function wrapper
214+
- `printDowncallBody()` - Body of the thunk
215+
216+
### "I want to add runtime support"
217+
218+
1. `Sources/SwiftJavaRuntimeSupport/` - Swift runtime helpers
219+
2. `Sources/SwiftJava/` - Core Swift-Java bridge types
220+
221+
## Data Flow Example: Escaping Closures
222+
223+
Here's how `@escaping` closures flow through the system:
224+
225+
```
226+
1. Swift Source
227+
func setCallback(callback: @escaping () -> Void)
228+
229+
2. Parsing (SwiftType.swift)
230+
SwiftType.function(SwiftFunctionType(isEscaping: true, ...))
231+
232+
3. Visiting (Swift2JavaVisitor.swift)
233+
Creates ImportedFunc with SwiftFunctionSignature containing the type
234+
235+
4. Translation (JNISwift2JavaGenerator+NativeTranslation.swift)
236+
translateParameter() sees isEscaping, returns:
237+
NativeParameter(conversion: .escapingClosureLowering(...))
238+
239+
5. Code Generation
240+
241+
Java (JNISwift2JavaGenerator+JavaBindingsPrinting.swift):
242+
@FunctionalInterface
243+
public interface callback { void apply(); }
244+
245+
Swift (JNISwift2JavaGenerator+SwiftThunkPrinting.swift):
246+
@_cdecl("Java_..._setCallback")
247+
func Java_..._setCallback(..., callback: jobject?) {
248+
let closure = {
249+
let context = JNIClosureContext(globalRef: ...)
250+
return { env.CallVoidMethod(...) }
251+
}()
252+
self.setCallback(callback: closure)
253+
}
254+
```
255+
256+
## Testing
257+
258+
### Unit Tests
259+
260+
**Directory:** `Tests/JExtractSwiftTests/`
261+
262+
```
263+
Tests/JExtractSwiftTests/
264+
├── JNI/
265+
│ ├── JNIClassTests.swift # Test class generation
266+
│ ├── JNIClosureTests.swift # Test closure handling
267+
│ ├── JNIStructTests.swift # Test struct generation
268+
│ └── ...
269+
└── FFM/
270+
└── ...
271+
```
272+
273+
### Integration Tests
274+
275+
**Directory:** `Samples/SwiftJavaExtractJNISampleApp/`
276+
277+
```bash
278+
# Build and run sample app
279+
./gradlew Samples:SwiftJavaExtractJNISampleApp:run
280+
281+
# Run Java tests
282+
./gradlew Samples:SwiftJavaExtractJNISampleApp:test
283+
```
284+
285+
## Quick Reference: Common Patterns
286+
287+
### Adding a conversion for a new type
288+
289+
```swift
290+
// In JNISwift2JavaGenerator+NativeTranslation.swift
291+
func translateParameter(...) throws -> NativeParameter {
292+
switch type {
293+
case .myNewType(let details):
294+
return NativeParameter(
295+
parameters: [...],
296+
conversion: .myNewConversion(...)
297+
)
298+
}
299+
}
300+
301+
// Add the conversion case
302+
enum NativeSwiftConversionStep {
303+
case myNewConversion(...)
304+
305+
func render(...) -> String {
306+
switch self {
307+
case .myNewConversion(...):
308+
// Generate conversion code
309+
return "..."
310+
}
311+
}
312+
}
313+
```
314+
315+
### Adding a new ImportedDecl type
316+
317+
```swift
318+
// In ImportedDecls.swift
319+
package final class ImportedMyThing: ImportedDecl {
320+
var name: String
321+
var details: ...
322+
}
323+
324+
// In Swift2JavaVisitor.swift
325+
func visit(myThingDecl node: MyThingSyntax, ...) {
326+
let imported = ImportedMyThing(...)
327+
// Add to translator's collection
328+
}
329+
```
330+
331+
## Module Dependency Graph
332+
333+
```
334+
SwiftJavaTool (CLI)
335+
336+
337+
JExtractSwiftLib (Core logic)
338+
339+
├──▶ SwiftTypes (Type representations)
340+
├──▶ JavaTypes (Java type representations)
341+
└──▶ SwiftJavaConfigurationShared (Config)
342+
343+
344+
SwiftJavaRuntimeSupport (Runtime helpers)
345+
346+
347+
SwiftJava (Core bridge)
348+
```
349+
350+
## Getting Help
351+
352+
- **Forum:** https://forums.swift.org
353+
- **GitHub Issues:** https://github.com/swiftlang/swift-java/issues
354+
- **Existing tests:** Best way to see patterns in action
355+

CONTRIBUTING.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,34 @@ your contribution to Apple and the community, and agree by submitting the patch
55
that your contributions are licensed under the Apache 2.0 license (see
66
`LICENSE.txt`).
77

8+
## Getting Started
9+
10+
Before diving into the code, read **[ARCHITECTURE.md](ARCHITECTURE.md)** to understand:
11+
12+
- The processing pipeline (Swift source → Intermediate Representation → Generated code)
13+
- Key components and what each file does
14+
- Where to make changes for different types of features
15+
- Data flow examples
16+
17+
This will give you the mental model needed to navigate the codebase effectively.
18+
19+
### Development Setup
20+
21+
```bash
22+
# Prerequisites: Java 22+ and Swift 6.0+
23+
export JAVA_HOME="$(sdk home java current)"
24+
25+
# Build the project
26+
swift build
27+
28+
# Run tests
29+
swift test
30+
./gradlew test
31+
32+
# Run sample app
33+
./gradlew Samples:SwiftJavaExtractJNISampleApp:run
34+
```
35+
836
## How to submit a bug report
937

1038
Please ensure to specify the following:

0 commit comments

Comments
 (0)