Skip to content

Commit 02047d9

Browse files
committed
refactor: move export generation to codegen
This finishes the refactor of codegen into the library, so the `Bindings` struct now just exports the `generate` method.
1 parent 94e10a3 commit 02047d9

4 files changed

Lines changed: 196 additions & 86 deletions

File tree

cmd/gravity/src/codegen/bindings.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ use wit_bindgen_core::wit_parser::{Resolve, SizeAlign, World};
55

66
use crate::{
77
codegen::{
8-
FactoryGenerator,
8+
ExportGenerator, FactoryGenerator,
9+
exports::ExportConfig,
910
factory::FactoryConfig,
1011
imports::{ImportAnalyzer, ImportCodeGenerator},
1112
ir::AnalyzedImports,
@@ -52,8 +53,18 @@ impl<'a> Bindings<'a> {
5253
Wasm::new(&self.raw_wasm_var, wasm).format_into(&mut self.out)
5354
}
5455

56+
/// Generate the bindings.
57+
///
58+
/// This generates the imports (interfaces, types, functions), the factory and instance
59+
/// type, and the exports (functions).
60+
pub fn generate(&mut self) {
61+
let (imports, chains) = self.generate_imports();
62+
self.generate_factory(&imports, chains);
63+
self.generate_exports(&imports.instance_name);
64+
}
65+
5566
/// Generates the imports for the bindings.
56-
pub fn generate_imports(&mut self) -> (AnalyzedImports, BTreeMap<String, Tokens<Go>>) {
67+
fn generate_imports(&mut self) -> (AnalyzedImports, BTreeMap<String, Tokens<Go>>) {
5768
let analyzer = ImportAnalyzer::new(self.resolve, self.world);
5869
let analyzed = analyzer.analyze();
5970

@@ -66,7 +77,7 @@ impl<'a> Bindings<'a> {
6677

6778
/// Generates the factory and instantiate functions, including any
6879
/// required interfaces.
69-
pub fn generate_factory(
80+
fn generate_factory(
7081
&mut self,
7182
analyzed_imports: &AnalyzedImports,
7283
import_chains: BTreeMap<String, Tokens<Go>>,
@@ -79,4 +90,19 @@ impl<'a> Bindings<'a> {
7990
};
8091
FactoryGenerator::new(config).format_into(&mut self.out)
8192
}
93+
94+
/// Generates all exports for the world.
95+
///
96+
/// Note: for now this only generates functions; types and interfaces are
97+
/// still TODO
98+
fn generate_exports(&mut self, instance: &GoIdentifier) {
99+
let config = ExportConfig {
100+
instance,
101+
go_imports: &self.go_imports,
102+
world: self.world,
103+
resolve: self.resolve,
104+
sizes: self.sizes,
105+
};
106+
ExportGenerator::new(config).format_into(&mut self.out)
107+
}
82108
}

cmd/gravity/src/codegen/exports.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
use genco::prelude::*;
2+
use wit_bindgen_core::wit_parser::{Function, Resolve, SizeAlign, World, WorldItem};
3+
4+
use crate::go::{GoIdentifier, GoImports, GoResult, GoType};
5+
6+
pub struct ExportConfig<'a> {
7+
pub instance: &'a GoIdentifier,
8+
pub go_imports: &'a GoImports,
9+
pub world: &'a World,
10+
pub resolve: &'a Resolve,
11+
pub sizes: &'a SizeAlign,
12+
}
13+
14+
pub struct ExportGenerator<'a> {
15+
config: ExportConfig<'a>,
16+
}
17+
18+
impl<'a> ExportGenerator<'a> {
19+
pub fn new(config: ExportConfig<'a>) -> Self {
20+
Self { config }
21+
}
22+
23+
fn generate_function(&self, func: &Function, tokens: &mut Tokens<Go>) {
24+
let params = func
25+
.params
26+
.iter()
27+
.map(
28+
|(name, wit_type)| match crate::resolve_type(wit_type, self.config.resolve) {
29+
GoType::ValueOrOk(t) => (GoIdentifier::local(name), *t),
30+
t => (GoIdentifier::local(name), t),
31+
},
32+
)
33+
.collect::<Vec<_>>();
34+
35+
let result = if let Some(wit_type) = &func.result {
36+
GoResult::Anon(crate::resolve_type(wit_type, self.config.resolve))
37+
} else {
38+
GoResult::Empty
39+
};
40+
41+
let mut f = crate::Func::export(result, self.config.sizes);
42+
wit_bindgen_core::abi::call(
43+
self.config.resolve,
44+
wit_bindgen_core::abi::AbiVariant::GuestExport,
45+
wit_bindgen_core::abi::LiftLower::LowerArgsLiftResults,
46+
func,
47+
&mut f,
48+
// async is not currently supported
49+
false,
50+
);
51+
52+
let arg_assignments = f
53+
.args()
54+
.iter()
55+
.zip(&params)
56+
.map(|(arg, (param, _))| (arg, param))
57+
.collect::<Vec<_>>();
58+
let fn_name = &GoIdentifier::public(&func.name);
59+
quote_in! { *tokens =>
60+
$['\n']
61+
func (i *$(self.config.instance)) $fn_name(
62+
$['\r']
63+
ctx $(&self.config.go_imports.context),
64+
$(for (name, typ) in &params join ($['\r']) => $name $typ,)
65+
) $(f.result()) {
66+
$(for (arg, param) in arg_assignments join ($['\r']) => $arg := $param)
67+
$(f.body())
68+
}
69+
}
70+
}
71+
}
72+
73+
impl FormatInto<Go> for ExportGenerator<'_> {
74+
fn format_into(self, tokens: &mut Tokens<Go>) {
75+
for item in self.config.world.exports.values() {
76+
match item {
77+
WorldItem::Function(func) => self.generate_function(func, tokens),
78+
WorldItem::Interface { .. } => todo!("generate interface exports"),
79+
WorldItem::Type(_) => todo!("generate type exports"),
80+
}
81+
}
82+
}
83+
}
84+
85+
#[cfg(test)]
86+
mod tests {
87+
use genco::prelude::*;
88+
use indexmap::indexmap;
89+
use wit_bindgen_core::wit_parser::{
90+
Function, FunctionKind, Resolve, SizeAlign, Type, World, WorldItem, WorldKey,
91+
};
92+
93+
use crate::go::{GoIdentifier, GoImports};
94+
95+
use super::{ExportConfig, ExportGenerator};
96+
97+
#[test]
98+
fn test_generate_function_simple_u32_param() {
99+
let func = Function {
100+
name: "add_number".to_string(),
101+
kind: FunctionKind::Freestanding,
102+
params: vec![("value".to_string(), Type::U32)],
103+
result: Some(Type::U32),
104+
docs: Default::default(),
105+
stability: Default::default(),
106+
};
107+
108+
let world = World {
109+
name: "test-world".to_string(),
110+
imports: indexmap! {},
111+
exports: indexmap! {
112+
WorldKey::Name("add-number".to_string()) => WorldItem::Function(func.clone())
113+
},
114+
docs: Default::default(),
115+
stability: Default::default(),
116+
includes: Default::default(),
117+
include_names: Default::default(),
118+
package: None,
119+
};
120+
121+
let resolve = Resolve::new();
122+
let mut sizes = SizeAlign::default();
123+
sizes.fill(&resolve);
124+
let instance = GoIdentifier::public("TestInstance");
125+
let go_imports = GoImports::new();
126+
127+
let config = ExportConfig {
128+
instance: &instance,
129+
go_imports: &go_imports,
130+
world: &world,
131+
resolve: &resolve,
132+
sizes: &sizes,
133+
};
134+
135+
let generator = ExportGenerator::new(config);
136+
let mut tokens = Tokens::new();
137+
138+
// Call the actual generate_function method
139+
generator.generate_function(&func, &mut tokens);
140+
141+
let generated = tokens.to_string().unwrap();
142+
println!("Generated: {}", generated);
143+
144+
// Verify basic function structure
145+
assert!(generated.contains("func (i *TestInstance) AddNumber("));
146+
assert!(generated.contains("value uint32"));
147+
assert!(generated.contains("ctx context.Context"));
148+
assert!(generated.contains(") uint32 {"));
149+
150+
// Verify function body
151+
assert!(generated.contains("arg0 := value"));
152+
assert!(
153+
generated.contains("i.module.ExportedFunction(\"add_number\").Call(ctx, uint64(arg0))")
154+
);
155+
assert!(generated.contains("if err0 != nil {"));
156+
assert!(generated.contains("panic(err0)"));
157+
assert!(generated.contains("results0 := raw0[0]"));
158+
assert!(generated.contains("result1 := uint32(results0)"));
159+
assert!(generated.contains("return result1"));
160+
}
161+
}

cmd/gravity/src/codegen/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
mod bindings;
2+
mod exports;
23
mod factory;
34
mod func;
45
mod imports;
56
mod ir;
67
mod wasm;
78

89
pub use bindings::*;
10+
pub use exports::ExportGenerator;
911
pub use factory::FactoryGenerator;
1012
pub use func::Func;
1113
pub use wasm::WasmData;

cmd/gravity/src/main.rs

Lines changed: 4 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
11
use std::{fs, path::Path, process::ExitCode};
22

33
use clap::{Arg, ArgAction, Command};
4-
use genco::{
5-
lang::{Go, go},
6-
quote_in,
7-
};
8-
use wit_bindgen_core::{
9-
abi::{AbiVariant, LiftLower},
10-
wit_parser::{SizeAlign, WorldItem},
11-
};
4+
use genco::lang::{Go, go};
5+
use wit_bindgen_core::wit_parser::SizeAlign;
126

13-
use arcjet_gravity::{
14-
Func,
15-
codegen::{Bindings, WasmData},
16-
go::{GoIdentifier, GoResult, GoType},
17-
resolve_type,
18-
};
7+
use arcjet_gravity::codegen::{Bindings, WasmData};
198

209
// `wit_component::decode` uses `root` as an arbitrary name for the primary
2110
// world name, see
@@ -76,10 +65,6 @@ fn main() -> Result<ExitCode, ()> {
7665

7766
let wasm_file = &format!("{}.wasm", selected_world.replace('-', "_"));
7867

79-
let instance = &GoIdentifier::public(format!("{selected_world}-instance"));
80-
81-
let context = &go::import("context", "Context");
82-
8368
let (_, world) = bindgen
8469
.resolve
8570
.worlds
@@ -97,71 +82,7 @@ fn main() -> Result<ExitCode, ()> {
9782
WasmData::Embedded(wasm_file)
9883
});
9984

100-
let (imports, chains) = bindings.generate_imports();
101-
bindings.generate_factory(&imports, chains);
102-
103-
// TODO: refactor exports into separate generators, too.
104-
for world_item in world.exports.values() {
105-
match world_item {
106-
WorldItem::Function(func) => {
107-
let mut params: Vec<(GoIdentifier, GoType)> = Vec::with_capacity(func.params.len());
108-
for (name, wit_type) in func.params.iter() {
109-
let go_type = resolve_type(wit_type, &bindgen.resolve);
110-
match go_type {
111-
// We can't represent this as an argument type so we unwrap the Some type
112-
// TODO: Figure out a better way to handle this
113-
GoType::ValueOrOk(typ) => params.push((GoIdentifier::local(name), *typ)),
114-
typ => params.push((GoIdentifier::local(name), typ)),
115-
}
116-
}
117-
118-
let mut sizes = SizeAlign::default();
119-
sizes.fill(&bindgen.resolve);
120-
121-
let result = match &func.result {
122-
Some(wit_type) => {
123-
let go_type = resolve_type(wit_type, &bindgen.resolve);
124-
GoResult::Anon(go_type)
125-
}
126-
None => GoResult::Empty,
127-
};
128-
129-
let mut f = Func::export(result, &sizes);
130-
wit_bindgen_core::abi::call(
131-
&bindgen.resolve,
132-
AbiVariant::GuestExport,
133-
LiftLower::LowerArgsLiftResults,
134-
func,
135-
&mut f,
136-
// async is not currently supported
137-
false,
138-
);
139-
140-
let arg_assignments = f
141-
.args()
142-
.iter()
143-
.zip(params.iter())
144-
.map(|(arg, (param, _))| (arg, param))
145-
.collect::<Vec<(&String, &GoIdentifier)>>();
146-
147-
let fn_name = &GoIdentifier::public(&func.name);
148-
// TODO(#16): Don't use the internal bindings.out field
149-
quote_in! { bindings.out =>
150-
$['\n']
151-
func (i *$instance) $fn_name(
152-
$['\r']
153-
ctx $context,
154-
$(for (name, typ) in params.iter() join ($['\r']) => $name $typ,)
155-
) $(f.result()) {
156-
$(for (arg, param) in arg_assignments join ($['\r']) => $arg := $param)
157-
$(f.body())
158-
}
159-
};
160-
}
161-
WorldItem::Interface { .. } => (),
162-
WorldItem::Type(_) => (),
163-
}
164-
}
85+
bindings.generate();
16586

16687
let header = "// Code generated by arcjet-gravity; DO NOT EDIT.\n\n".to_string();
16788
let mut w = genco::fmt::FmtWriter::new(header);

0 commit comments

Comments
 (0)