Skip to content

Commit ec17a36

Browse files
authored
feat: Make C library bindings support allocating returns. (#107)
* Make C library bindings support allocating returns. Fixes #106 * Refactor LibModules and CFunctionBindingGenerator to be simpler
1 parent ce66379 commit ec17a36

File tree

6 files changed

+219
-200
lines changed

6 files changed

+219
-200
lines changed

core/benchmarks/test/src/fr/hammons/slinc/BindingsBenchmarkShape.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import java.util.concurrent.TimeUnit
55
import scala.util.Random
66
import scala.annotation.nowarn
77
import fr.hammons.slinc.modules.LibModule
8+
import org.openjdk.jmh.infra.Blackhole
89

910
case class div_t(quot: Int, rem: Int)
1011

@@ -61,6 +62,17 @@ trait BindingsBenchmarkShape(val s: Slinc):
6162
)
6263
}
6364

65+
val path = Random.nextBoolean()
66+
67+
@Benchmark
68+
def ifmark(blackhole: Blackhole) =
69+
if path then blackhole.consume(1 + 2)
70+
else blackhole.consume(2 + 3)
71+
72+
@Benchmark
73+
def noifmark(blackhole: Blackhole) =
74+
blackhole.consume(1 + 2)
75+
6476
@Benchmark
6577
def abs =
6678
Cstd.abs(6)
Lines changed: 109 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,113 @@
11
package fr.hammons.slinc
22

3-
import java.lang.invoke.MethodHandle
43
import scala.quoted.*
54
import scala.annotation.nowarn
5+
import fr.hammons.slinc.CFunctionRuntimeInformation.{
6+
InputTransition,
7+
ReturnTransition
8+
}
9+
import fr.hammons.slinc.CFunctionBindingGenerator.VariadicTransition
610

7-
type InputTransition = (Allocator, Any) => Any
8-
type OutputTransition = (Object | Null) => AnyRef
9-
type VariadicInputTransition = (TypeDescriptor, Allocator, Any) => Any
1011
trait CFunctionBindingGenerator:
1112
def generate(
1213
methodHandler: MethodHandler,
13-
inputTransitions: IArray[InputTransition],
14-
outputTransition: OutputTransition,
15-
scope: Scope
16-
): AnyRef
17-
18-
def generateVariadic(
19-
methodHandler: MethodHandler,
20-
inputTransitions: IArray[InputTransition],
21-
variadicTransition: VariadicInputTransition,
22-
outputTransition: OutputTransition,
14+
transitionSet: CFunctionRuntimeInformation,
15+
variadicTransition: VariadicTransition,
2316
scope: Scope
2417
): AnyRef
2518

2619
object CFunctionBindingGenerator:
27-
private def getVariadicExprs(
28-
s: Seq[Variadic],
29-
variadicTransition: VariadicInputTransition,
30-
allocator: Allocator
31-
): Seq[Any] =
32-
s.map: vararg =>
33-
vararg.use[DescriptorOf](dc ?=>
34-
d => variadicTransition(dc.descriptor, allocator, d)
35-
)
20+
type VariadicTransition = (Allocator, Seq[Variadic]) => Seq[Any]
21+
22+
private enum LambdaInputs:
23+
case Standard(args: List[Expr[Any]])
24+
case VariadicInputs(args: List[Expr[Any]], varArgs: Expr[Seq[Variadic]])
25+
26+
private object LambdaInputs:
27+
def choose(args: List[Expr[Any]], isVariadic: Boolean)(
28+
variadicInput: => Expr[Seq[Variadic]]
29+
) = if isVariadic then
30+
LambdaInputs.VariadicInputs(args, varArgs = variadicInput)
31+
else LambdaInputs.Standard(args)
3632

3733
@nowarn("msg=unused implicit parameter")
38-
private def invokeVariadicArguments(
39-
mhGen: Expr[Seq[Variadic] => MethodHandle],
40-
inputs: Expr[Seq[Any]],
41-
varArgs: Expr[Seq[Variadic]],
42-
variadicTransition: Expr[VariadicInputTransition],
43-
alloc: Expr[Allocator]
34+
private def invokation(
35+
variadicTransition: Expr[VariadicTransition],
36+
mh: Expr[MethodHandler]
4437
)(using Quotes) =
45-
'{
46-
MethodHandleFacade.callVariadic(
47-
$mhGen($varArgs),
48-
$inputs ++ getVariadicExprs($varArgs, $variadicTransition, $alloc)*
49-
)
50-
}
38+
(alloc: Expr[Allocator], inputs: LambdaInputs) =>
39+
inputs match
40+
case LambdaInputs.Standard(args) =>
41+
MethodHandleTools.invokeArguments(
42+
'{ $mh.nonVariadic },
43+
args
44+
)
45+
case LambdaInputs.VariadicInputs(args, varArgs) =>
46+
'{
47+
MethodHandleFacade.callVariadic(
48+
$mh.variadic($varArgs),
49+
${ Expr.ofList(args) } ++ $variadicTransition($alloc, $varArgs)*
50+
)
51+
}
5152

5253
inline def apply[L](
5354
name: String
5455
): CFunctionBindingGenerator = ${
5556
applyImpl[L]('name)
5657
}
5758

59+
@nowarn("msg=unused implicit parameter")
60+
private def lambda(
61+
argNumbers: Int,
62+
scope: Expr[Scope],
63+
inputTransitions: Expr[IArray[InputTransition]],
64+
outputTransition: Expr[ReturnTransition],
65+
allocatingReturn: Boolean,
66+
varArg: Boolean
67+
)(
68+
invocationExpr: Quotes ?=> (
69+
Expr[Allocator],
70+
LambdaInputs
71+
) => Expr[Object | Null]
72+
)(using Quotes) =
73+
import quotes.reflect.*
74+
75+
val names = List.fill(argNumbers)("a")
76+
val argTypes =
77+
if varArg then
78+
List.fill(argNumbers - 1)(TypeRepr.of[Object]) :+ TypeRepr
79+
.of[Seq[Variadic]]
80+
else List.fill(argNumbers)(TypeRepr.of[Object])
81+
82+
val methodType =
83+
MethodType(names)(_ => argTypes, _ => TypeRepr.of[Object])
84+
85+
Lambda(
86+
Symbol.spliceOwner,
87+
methodType,
88+
(sym, inputs) =>
89+
def inputExprs(alloc: Expr[Allocator])(using q: Quotes) =
90+
val prefix = if allocatingReturn then List(alloc.asTerm) else Nil
91+
val toTransform = if varArg then inputs.init else inputs
92+
LambdaInputs.choose(
93+
prefix
94+
.concat(toTransform)
95+
.map(_.asExpr)
96+
.zipWithIndex
97+
.map: (exp, i) =>
98+
'{ $inputTransitions(${ Expr(i) })($alloc, $exp) },
99+
varArg
100+
)(inputs.last.asExprOf[Seq[Variadic]])
101+
102+
'{
103+
$scope { alloc ?=>
104+
$outputTransition(
105+
${ invocationExpr('alloc, inputExprs('alloc)) }
106+
)
107+
}
108+
}.asTerm.changeOwner(sym)
109+
).asExprOf[AnyRef]
110+
58111
@nowarn("msg=unused implicit parameter")
59112
private def applyImpl[L](name: Expr[String])(using
60113
Quotes,
@@ -69,124 +122,33 @@ object CFunctionBindingGenerator:
69122
.declaredMethod(name.valueOrAbort)
70123
.head
71124

72-
val argNumbers = methodSymbol.paramSymss.map(_.size).sum
73-
val names = List.fill(argNumbers)("a")
74-
75-
def lambda(
76-
methodHandle: Expr[MethodHandle],
77-
inputTransitions: Expr[IArray[InputTransition]],
78-
outputTransition: Expr[OutputTransition],
79-
scope: Expr[Scope]
80-
)(using Quotes): Expr[AnyRef] =
81-
import quotes.reflect.*
82-
83-
val argsTypes = List.fill(argNumbers)(TypeRepr.of[Object])
84-
85-
val methodType =
86-
MethodType(names)(_ => argsTypes, _ => TypeRepr.of[Object])
87-
Lambda(
88-
Symbol.spliceOwner,
89-
methodType,
90-
(sym, inputs) =>
91-
def inputExprs(using q: Quotes) = (alloc: Expr[Allocator]) =>
92-
inputs
93-
.map(i => i.asExpr)
94-
.zipWithIndex
95-
.map((exp, i) =>
96-
'{ $inputTransitions(${ Expr(i) })($alloc, $exp) }
97-
)
98-
'{
99-
$scope { alloc ?=>
100-
$outputTransition(
101-
${
102-
MethodHandleTools.invokeArguments(
103-
methodHandle,
104-
inputExprs('alloc)
105-
)
106-
}
107-
)
108-
}
109-
}.asTerm.changeOwner(sym)
110-
).asExprOf[AnyRef]
111-
112-
def lambdaVariadic(
113-
methodHandleGen: Expr[Seq[Variadic] => MethodHandle],
114-
inputTransitions: Expr[IArray[InputTransition]],
115-
outputTransition: Expr[OutputTransition],
116-
variadicTransition: Expr[
117-
VariadicInputTransition
118-
],
119-
scope: Expr[Scope]
120-
)(using Quotes): Expr[AnyRef] =
121-
import quotes.reflect.*
122-
123-
val argTypes = List.fill(argNumbers - 1)(TypeRepr.of[Object]) :+ TypeRepr
124-
.of[Seq[Variadic]]
125-
126-
val methodType =
127-
MethodType(names)(_ => argTypes, _ => TypeRepr.of[Object])
128-
129-
Lambda(
130-
Symbol.spliceOwner,
131-
methodType,
132-
(sym, inputs) =>
133-
def inputExprs(using q: Quotes) = (alloc: Expr[Allocator]) =>
134-
Expr.ofList(
135-
inputs.init
136-
.map(i => i.asExpr)
137-
.zipWithIndex
138-
.map((exp, i) =>
139-
'{ $inputTransitions(${ Expr(i) })($alloc, $exp) }
140-
)
141-
)
142-
143-
'{
144-
$scope { alloc ?=>
145-
$outputTransition(
146-
${
147-
invokeVariadicArguments(
148-
methodHandleGen,
149-
inputExprs('alloc),
150-
inputs.last.asExprOf[Seq[Variadic]],
151-
variadicTransition,
152-
'{ alloc }
153-
)
154-
}
155-
)
156-
}
157-
}.asTerm.changeOwner(sym)
158-
).asExprOf[AnyRef]
159-
160125
'{
161126
new CFunctionBindingGenerator:
162127
def generate(
163128
methodHandler: MethodHandler,
164-
inputTransitions: IArray[InputTransition],
165-
outputTransition: OutputTransition,
129+
functionInformation: CFunctionRuntimeInformation,
130+
variadicTransition: VariadicTransition,
166131
scope: Scope
167132
): AnyRef =
168133
${
169-
lambda(
170-
'{ methodHandler.nonVariadic },
171-
'inputTransitions,
172-
'outputTransition,
173-
'scope
174-
)
134+
def lambdaGen(allocatingReturn: Boolean, variadic: Boolean) =
135+
lambda(
136+
methodSymbol.paramSymss.map(_.size).sum,
137+
'scope,
138+
'{ functionInformation.inputTransitions },
139+
'{ functionInformation.returnTransition },
140+
allocatingReturn,
141+
variadic
142+
)(invokation('variadicTransition, 'methodHandler))
143+
144+
'{
145+
if functionInformation.isVariadic && functionInformation.returnAllocates
146+
then ${ lambdaGen(allocatingReturn = true, variadic = true) }
147+
else if functionInformation.isVariadic then
148+
${ lambdaGen(allocatingReturn = false, variadic = true) }
149+
else if functionInformation.returnAllocates then
150+
${ lambdaGen(allocatingReturn = true, variadic = false) }
151+
else ${ lambdaGen(allocatingReturn = false, variadic = false) }
152+
}
175153
}
176-
177-
def generateVariadic(
178-
methodHandler: MethodHandler,
179-
inputTransitions: IArray[InputTransition],
180-
variadicTransitions: VariadicInputTransition,
181-
outputTransition: OutputTransition,
182-
scope: Scope
183-
): AnyRef = ${
184-
lambdaVariadic(
185-
'{ methodHandler.variadic },
186-
'inputTransitions,
187-
'outputTransition,
188-
'variadicTransitions,
189-
'scope
190-
)
191-
}
192154
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package fr.hammons.slinc
2+
3+
import fr.hammons.slinc.modules.TransitionModule
4+
import fr.hammons.slinc.CFunctionRuntimeInformation.InputTransition
5+
import fr.hammons.slinc.CFunctionRuntimeInformation.ReturnTransition
6+
7+
final case class CFunctionRuntimeInformation(
8+
name: String,
9+
inputDescriptors: Seq[TypeDescriptor],
10+
inputTransitions: IArray[InputTransition],
11+
returnDescriptor: Option[TypeDescriptor],
12+
returnTransition: ReturnTransition,
13+
isVariadic: Boolean,
14+
returnAllocates: Boolean
15+
)
16+
17+
object CFunctionRuntimeInformation:
18+
def apply(functionDescriptor: CFunctionDescriptor)(using
19+
transitionModule: TransitionModule
20+
) =
21+
val allocatingReturn = functionDescriptor.returnDescriptor
22+
.map:
23+
case ad: AliasDescriptor[?] => ad.real
24+
case a => a
25+
.exists:
26+
case _: StructDescriptor => true
27+
case _ => false
28+
29+
val allocationTransition: Seq[InputTransition] =
30+
if allocatingReturn then
31+
Seq((allocator, _) => transitionModule.methodArgument(allocator))
32+
else Seq.empty
33+
34+
val inputTransitions: Seq[InputTransition] =
35+
functionDescriptor.inputDescriptors.map: typeDescriptor =>
36+
(allocator, input) =>
37+
transitionModule.methodArgument(typeDescriptor, input, allocator)
38+
val outputTransition: ReturnTransition =
39+
functionDescriptor.returnDescriptor match
40+
case None => _ => ().asInstanceOf[Object]
41+
case Some(descriptor) =>
42+
returnValue =>
43+
transitionModule.methodReturn[Object](descriptor, returnValue.nn)
44+
45+
new CFunctionRuntimeInformation(
46+
functionDescriptor.name,
47+
functionDescriptor.inputDescriptors,
48+
IArray.from(allocationTransition ++ inputTransitions),
49+
functionDescriptor.returnDescriptor,
50+
outputTransition,
51+
functionDescriptor.isVariadic,
52+
allocatingReturn
53+
)
54+
55+
type InputTransition = (Allocator, Any) => Any
56+
type ReturnTransition = (Object | Null) => AnyRef

core/test/src/fr/hammons/slinc/LibSpec.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,18 @@ trait LibSpec(val slinc: Slinc) extends ScalaCheckSuite {
6868
}
6969
}
7070

71+
test("allocating returns") {
72+
import slinc.given
73+
case class div_t(quot: Int, rem: Int) derives Struct
74+
75+
trait AllocatingLib derives Lib:
76+
def div(i: Int, r: Int): div_t
77+
78+
val allocatingLib = Lib.instance[AllocatingLib]
79+
80+
assertEquals(allocatingLib.div(5, 2), div_t(2, 1))
81+
}
82+
7183
test("platform dependent types") {
7284
val maybeError = compileErrors("""
7385
trait PlatformLib derives Lib:

0 commit comments

Comments
 (0)