Skip to content

Commit 6ba6865

Browse files
committed
fixup docs
1 parent 5f64ecf commit 6ba6865

File tree

3 files changed

+71
-106
lines changed

3 files changed

+71
-106
lines changed

Diff for: README.md

+44-101
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,18 @@ By default, we test the following pairings:
5656
In theory other implementations aren't *too bad* to add. You just need to:
5757

5858
* Add an implementation of abis::AbiImpl
59-
* Specify the name, language, and source-file extension
60-
* Specify supported calling conventions
61-
* Specify how to generate a caller from a signature
62-
* Specify how to generate a callee from a signature
59+
* Specify the language and source-file extension
60+
* Specify how to generate source for a caller from a signature
61+
* Specify how to generate source for a callee from a signature
6362
* Specify how to compile a source file to a static lib
6463
* Register it in the `abi_impls` map in `fn main`
6564
* (Optional) Register what you want it paired with by default in `DEFAULT_TEST_PAIRS`
6665
* i.e. (ABI_IMPL_YOU, ABI_IMPL_CC) will have the harness test you calling into C
6766

67+
The bulk of the work is specifying how to generate source code, which can be done
68+
incrementally by return UnimplementedError to indicate unsupported features. This
69+
is totally fine, all the backends have places where they give up!
70+
6871
See the Test Harness section below for details on how to use it.
6972

7073

@@ -83,98 +86,37 @@ Windows Conventions:
8386
* cdecl
8487
* fastcall
8588
* stdcall
86-
* ~~vectorcall~~ (code is there, but disabled due to linking issues)
87-
88-
Any test which specifies the "All" will implicitly combinatorically generate every known convention.
89-
"Nonsensical" situations like stdcall on linux are the responsibility of the AbiImpls to identify and disable.
89+
* vectorcall
9090

9191

9292
## Types
9393

94-
The test format support for the following types/concepts:
95-
96-
* fixed-width integer types (uint8_t and friends)
97-
* float/double
98-
* bool
99-
* structs
100-
* c-like enums
101-
* c-like untagged unions
102-
* rust-like tagged unions
103-
* opaque pointers (void\*)
104-
* pass-by-ref (still checks the pointee's layout, and not the address)
105-
* arrays (including multi-dimensional arrays, although C often requires arrays to be wrapped in pass-by-ref)
94+
The abi-cafe typesystem is [defined by kdl-script](https://github.com/Gankra/abi-cafe/blob/main/kdl-script/README.md#types), see those docs for details, but, we basically support most of the types you could define/use in core Rust.
10695

10796

10897
# Adding Tests
10998

110-
The default suite of tests can be found in `/include/tests/`
99+
Tests are specified as [kdl-script](https://github.com/Gankra/abi-cafe/blob/main/kdl-script/README.md) files, which are basically C header files, but with a custom syntax that avoids us tying our hands to any particular language/semantics. The syntax will feel fairly familiar to Rust programmers.
111100

112-
There are two kinds of tests: `.kdl` ("normal") and `.procgen.kdl` ("procgen").
101+
The default suite of tests can be [found in `/include/tests/`](https://github.com/Gankra/abi-cafe/tree/main/include/tests), which is statically embedded in abi-cafe's binary. You don't need to register the test anywhere, we will just try to parse every file in the tests directory.
113102

114-
The latter is sugar for the former, where you just define a type with the same name of the file (so `MetersU32.procgen.kdl` is expected to define a type named `MetersU32`), and we generate a battery of types/functions that stress it out.
103+
There are two kinds of tests: `.kdl` ("[normal](https://github.com/Gankra/abi-cafe/tree/main/include/tests/normal)") and `.procgen.kdl` ("[procgen](https://github.com/Gankra/abi-cafe/tree/main/include/tests/procgen)").
115104

116-
Tests are specified as [kdl-script](https://github.com/ron-rs/ron) files in the test/ directory, because it's more compact than JSON, has comments, and is more reliable with large integers. Because C is in some sense the "lingua franca" of FFI that everyone has to deal with, we prefer using C types in these definitions.
117-
118-
You don't need to register the test anywhere, we will just try to parse every file in that directory.
119-
120-
The "default" workflow is to handwrite a ron file, and the testing framework will handle generating the actual code implementating that interface (example: structs.ron). Generated impls will be output to the generated_impls dir for debugging. Build artifacts will get dumped in target/temp/ if you want to debug those too.
121-
122-
Example:
123-
124-
```rust
125-
Test(
126-
// name of this set of tests
127-
name: "examples",
128-
// generate tests for the following function signatures
129-
funcs: [
130-
(
131-
// base function/subtest name (but also subtest name)
132-
name: "some_prims",
133-
// what calling conventions to generate this test for
134-
// (All = do them all, a good default)
135-
conventions: [All],
136-
// args
137-
inputs: [
138-
Int(c_int32_t(5)),
139-
Int(c_uint64_t(0x123_abc)),
140-
]
141-
// return value
142-
output: Some(Bool(true)),
143-
),
144-
(
145-
name: "some_structs",
146-
conventions: [All],
147-
inputs: [
148-
Int(c_int32_t(5)),
149-
// Struct decls are implicit in usage.
150-
// All structs with the same name must match!
151-
Struct("MyStruct", [
152-
Int(c_uint8_t(0xf1)),
153-
Float(c_double(1234.23)),
154-
]),
155-
Struct("MyStruct", [
156-
Int(c_uint8_t(0x1)),
157-
Float(c_double(0.23)),
158-
]),
159-
],
160-
// no return (void)
161-
output: None,
162-
),
163-
]
164-
)
165-
```
166-
167-
However, you have two "power user" options available:
105+
The latter is sugar for the former, where you just define a type with the same name of the file (so `MetersU32.procgen.kdl` is expected to define a type named `MetersU32`), and we generate a battery of types/functions that stress it out.
168106

169-
* Generate the ron itself with generate_procedural_tests in main.rs (example: ui128.ron). This is good for bruteforcing a bunch of different combinations if you just want to make sure a type/feature generally works in many different situations.
107+
Suggested Examples:
170108

171-
* Use the "Handwritten" convention and manually provide the implementations (example: opaque_example.ron). This lets you basically do *anything* without the testing framework having to understand your calling convention or type/feature. Manual impls go in handwritten_impls and use the same naming/structure as generated_impls.
109+
* [simple.kdl](https://github.com/Gankra/abi-cafe/blob/main/include/tests/normal/simple.kdl) - a little example of a "normal" test with explicitly defined functions to test
110+
* [SimpleStruct.procgen.kdl](https://github.com/Gankra/abi-cafe/blob/main/include/tests/procgen/struct/SimpleStruct.procgen.kdl) - similar to simple.kdl, but procgen
111+
* [MetersU32.procgen.kdl](https://github.com/Gankra/abi-cafe/blob/main/include/tests/procgen/pun/MetersU32.procgen.kdl) - an example of a ["pun type"](https://github.com/Gankra/abi-cafe/tree/main/kdl-script#pun-types), where different languages use different definitions
112+
* [IntrusiveList.procgen.kdl](https://github.com/Gankra/abi-cafe/blob/main/include/tests/procgen/fancy/IntrusiveList.procgen.kdl) - an example of how we can procgen tests for self-referential types and tagged unions
113+
* [i8.procgen.kdl](https://github.com/Gankra/abi-cafe/blob/main/include/tests/procgen/primitive/i8.procgen.kdl) - ok this one isn't instructive it's just funny that it can be a blank file because i8 is builtin so all the info needed is in the filename
172114

173115

174116

175117
# The Test Harness
176118

177-
Implementation details of dylib test harness are split up between main.rs and the contents of the top-level harness/ directory. The contents of harness/ include:
119+
Implementation details of dylib test harness are split up between [src/harness/run.rs](https://github.com/Gankra/abi-cafe/blob/main/src/harness/run.rs) and the contents of the top-level [/include/harness/](https://github.com/Gankra/abi-cafe/blob/main/include/harness/). The contents of /include/harness/ are:
178120

179121
* "headers" for the testing framework for each language
180122
* harness.rs, which defines the entry-point for the test and sets up all the global callbacks/pointers. This is linked with the callee and caller to create the final dylib.
@@ -183,50 +125,50 @@ Ideally you shouldn't have to worry about *how* the callbacks work, so I'll just
183125

184126
```C
185127
// Caller Side
186-
uint64_t basic_val(struct MyStruct arg0, int32_t arg1);
128+
uint64_t basic_val(MyStruct arg0, int32_t arg1);
187129

188130
// The test harness will invoke your test through this symbol!
189131
void do_test(void) {
190132
// Initialize and report the inputs
191-
struct MyStruct arg0 = { 241, 1234.23 };
192-
WRITE(CALLER_INPUTS, (char*)&arg0.field0, (uint32_t)sizeof(arg0.field0));
193-
WRITE(CALLER_INPUTS, (char*)&arg0.field1, (uint32_t)sizeof(arg0.field1));
194-
FINISHED_VAL(CALLER_INPUTS);
133+
MyStruct arg0 = { 241, 1234.23 };
134+
write_field(CALLER_INPUTS, arg0.field0);
135+
write_filed(CALLER_INPUTS, arg0.field1);
136+
finished_val(CALLER_INPUTS);
195137

196138
int32_t arg1 = 5;
197-
WRITE(CALLER_INPUTS, (char*)&arg1, (uint32_t)sizeof(arg1));
198-
FINISHED_VAL(CALLER_INPUTS);
139+
write_field(CALLER_INPUTS, arg1);
140+
finished_val(CALLER_INPUTS);
199141

200142
// Do the call
201143
uint64_t output = basic_val(arg0, arg1);
202144

203145
// Report the output
204-
WRITE(CALLER_OUTPUTS, (char*)&output, (uint32_t)sizeof(output));
205-
FINISHED_VAL(CALLER_OUTPUTS);
146+
write_field(CALLER_OUTPUTS, output);
147+
finished_val(CALLER_OUTPUTS);
206148

207149
// Declare that the test is complete on our side
208-
FINISHED_FUNC(CALLER_INPUTS, CALLER_OUTPUTS);
150+
finished_func(CALLER_INPUTS, CALLER_OUTPUTS);
209151
}
210152
```
211153
212154
```C
213155
// Callee Side
214-
uint64_t basic_val(struct MyStruct arg0, int32_t arg1) {
156+
uint64_t basic_val(MyStruct arg0, int32_t arg1) {
215157
// Report the inputs
216-
WRITE(CALLEE_INPUTS, (char*)&arg0.field0, (uint32_t)sizeof(arg0.field0));
217-
WRITE(CALLEE_INPUTS, (char*)&arg0.field1, (uint32_t)sizeof(arg0.field1));
218-
FINISHED_VAL(CALLEE_INPUTS);
158+
write_field(CALLEE_INPUTS, arg0.field0);
159+
write_field(CALLEE_INPUTS, arg0.field1);
160+
finished_val(CALLEE_INPUTS);
219161
220-
WRITE(CALLEE_INPUTS, (char*)&arg1, (uint32_t)sizeof(arg1));
221-
FINISHED_VAL(CALLEE_INPUTS);
162+
write_field(CALLEE_INPUTS, arg1);
163+
finished_val(CALLEE_INPUTS);
222164
223165
// Initialize and report the output
224166
uint64_t output = 17;
225-
WRITE(CALLEE_OUTPUTS, (char*)&output, (uint32_t)sizeof(output));
226-
FINISHED_VAL(CALLEE_OUTPUTS);
167+
write_field(CALLEE_OUTPUTS, output);
168+
finished_val(CALLEE_OUTPUTS);
227169
228170
// Declare that the test is complete on our side
229-
FINISHED_FUNC(CALLEE_INPUTS, CALLEE_OUTPUTS);
171+
finished_func(CALLEE_INPUTS, CALLEE_OUTPUTS);
230172
231173
// Actually return
232174
return output;
@@ -235,13 +177,14 @@ uint64_t basic_val(struct MyStruct arg0, int32_t arg1) {
235177

236178
The high level idea is that each side:
237179

238-
* Uses WRITE to report individual fields of values (to avoid padding)
239-
* Uses FINISHED_VAL to specify that all fields for a value have been written
240-
* Uses FINISHED_FUNC to specify that the current function is done (the caller will usually contain many subtests, FINISHED_FUNC delimits those)
180+
* Uses write_field to report individual fields of values (to avoid padding)
181+
* Uses finished_val to specify that all fields for a value have been written
182+
* Uses finished_func to specify that the current function is done (the caller will usually contain many subtests, FINISHED_FUNC delimits those)
241183

242184
There are 4 buffers: CALLER_INPUTS, CALLER_OUTPUTS, CALLEE_INPUTS, CALLEE_OUTPUTS. Each side should only use its two buffers.
243185

244-
The signatures of the callbacks are:
186+
The signatures of the callbacks are as follows, but each language wraps these
187+
in functions/macros to keep the codegen readable:
245188

246189
* `WRITE(Buffer buffer, char* input, uint32_t size_of_input)`
247190
* `FINISH_VAL(Buffer buffer)`

Diff for: include/tests/normal/simple.kdl

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// This is a simple little test case to demonstrate a basic normal
2+
// abi-cafe test that includes some custom types and functions
3+
4+
struct "Point3" {
5+
x "f32"
6+
y "f32"
7+
z "f32"
8+
}
9+
10+
fn "print" {
11+
inputs { _ "Point3"; }
12+
}
13+
14+
fn "scale" {
15+
inputs { _ "Point3"; factor "f32"; }
16+
output { "Point3"; }
17+
}
18+
19+
fn "add" {
20+
inputs { _ "Point3"; _ "Point3"; }
21+
outputs { _ "Point3"; }
22+
}

Diff for: include/tests/normal/types.kdl renamed to include/tests/normal/typemess.kdl

+5-5
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@ enum "ErrorCode" {
2020

2121
tagged "OptionI32" {
2222
None
23-
Some { _0 "i32"; }
23+
Some { _ "i32"; }
2424
}
2525

2626
tagged "MyResult" {
27-
Ok { _0 "[u32;3]"; }
28-
Err { _0 "ErrorCode"; }
27+
Ok { _ "[u32;3]"; }
28+
Err { _ "ErrorCode"; }
2929
}
3030

3131
tagged "MyDeepResult" {
32-
Ok { _0 "MyResult"; }
33-
Err { _0 "OptionI32"; }
32+
Ok { _ "MyResult"; }
33+
Err { _ "OptionI32"; }
3434
FileNotFound { x "bool"; y "Simple"; }
3535
}
3636

0 commit comments

Comments
 (0)