11import * as wasm from '../backend/wasm.js'
2+ import * as types from '../frontend/types.js'
3+ import * as ast from '../frontend/ast.js'
4+ import { Binding , MIR , Method } from '../frontend/modules.js'
5+ import { lowerpattern } from '../frontend/patterns.js'
6+ import { xcall , xlist , xpart } from '../frontend/lower.js'
27import { Options , withOptions } from '../utils/options.js'
8+ import { unreachable } from '../utils/ir.js'
9+ import { reset } from '../utils/cache.js'
310import * as path from 'path'
411import { chmod , mkdir , readFile , writeFile } from 'fs/promises'
512import { spawn , SpawnOptions } from 'node:child_process'
613import { dirname } from './dirname.js'
714import { Compiler } from '../backend/compiler.js'
15+ import type { Sig } from '../middle/abstract.js'
16+ import { Def } from '../dwarf/index.js'
817
918export { Compiler , compile , compileJS , exec , load }
1019
@@ -20,6 +29,7 @@ interface CompileConfig {
2029 compiler ?: Compiler
2130 options ?: Partial < Options >
2231 output ?: string
32+ embed ?: boolean
2333 strip ?: boolean
2434}
2535
@@ -37,21 +47,110 @@ async function compile(file: string, config: CompileConfig = {}): Promise<[Compi
3747 return [ compiler ! , wasmPath ]
3848}
3949
50+ function isJSIdentifier ( name : string ) : boolean {
51+ return / ^ [ $ A - Z _ ] [ 0 - 9 A - Z _ $ ] * $ / i. test ( name )
52+ }
53+
54+ function buildPaths ( file : string , dir : string , output ?: string ) : { js : string , wasm : string } {
55+ if ( ! output ) {
56+ const base = path . basename ( file , path . extname ( file ) )
57+ return {
58+ js : path . join ( dir , `${ base } .js` ) ,
59+ wasm : path . join ( dir , `${ base } .wasm` )
60+ }
61+ }
62+ const { dir : outDir , name, ext } = path . parse ( output )
63+ const wasmBase = ext ? path . join ( outDir , name ) : output
64+ return { js : output , wasm : `${ wasmBase } .wasm` }
65+ }
66+
67+ function exportedFunctions ( compiler : Compiler ) : [ string , types . Tag ] [ ] {
68+ const mod = compiler . pipe . sources . module ( types . tag ( '' ) )
69+ const out : [ string , types . Tag ] [ ] = [ ]
70+ for ( const name of [ ...mod . exports ] . sort ( ) ) {
71+ const value = compiler . pipe . defs . resolve_static ( new Binding ( types . tag ( '' ) , name ) )
72+ if ( ! ( value instanceof types . Tag ) ) continue
73+ out . push ( [ name , value ] )
74+ }
75+ return out
76+ }
77+
78+ // TODO better to have a generic means for converting to JS functions. Exported
79+ // globals can implicitly convert to JS, and we don't need to wrap.
80+ function libWrapperIR ( name : string , f : types . Tag ) : MIR {
81+ const rcall = ( code : MIR , fn : types . Tag | Method , args : any [ ] ) => {
82+ const arglist = code . push ( code . stmt ( xlist ( ...args ) ) )
83+ const result = code . push ( code . stmt ( xcall ( fn , arglist ) ) )
84+ return code . push ( code . stmt ( xpart ( result , types . Type ( 1n ) ) ) )
85+ }
86+ const code = MIR ( Def ( name ) )
87+ const args = code . argument ( unreachable )
88+ const jsargs = rcall ( code , types . tag ( 'common.JSObject' ) , [ args ] )
89+ const rvargs = rcall ( code , types . tag ( 'common.collect' ) , [ jsargs ] )
90+ const result = code . push ( code . stmt ( xcall ( f , rvargs ) ) )
91+ const value = code . push ( code . stmt ( xpart ( result , types . Type ( 1n ) ) ) )
92+ const jsresult = rcall ( code , types . tag ( 'common.js' ) , [ value ] )
93+ code . return ( code . push ( code . stmt ( xpart ( jsresult , types . Type ( 1n ) ) ) ) )
94+ return code
95+ }
96+
97+ function emitSig ( compiler : Compiler , em : wasm . BatchEmitter , sig : Sig , main = true ) : string {
98+ reset ( compiler . pipe )
99+ const func = compiler . pipe . wasm . get ( sig )
100+ const calls = wasm . calltree ( compiler . pipe . wasm , func )
101+ const start = em . main . length
102+ em . emit ( calls , func )
103+ if ( ! main ) em . main . length = start
104+ return func . name
105+ }
106+
107+ function jsRuntime ( exports : [ string , string ] [ ] , runtime : string , config : { wasmFile ?: string , base64 ?: string , memcheck ?: boolean } ) : string {
108+ const { wasmFile, base64, memcheck = false } = config
109+ const init = base64
110+ ? `\nconst __raven = await __ravenInline(${ JSON . stringify ( base64 ) } , ${ memcheck } )\n`
111+ : `\nconst __raven = await __ravenLib(${ JSON . stringify ( wasmFile ) } , ${ memcheck } )\n`
112+ const wrappers = exports . map ( ( [ name , wasmName ] , i ) => {
113+ const fn = `__raven_fn_${ i } `
114+ return `const ${ fn } = __raven(${ JSON . stringify ( wasmName ) } )
115+ export const ${ name } = (...args) => ${ fn } (args)`
116+ } ) . join ( '\n' )
117+ return `${ runtime } ${ init } ${ wrappers } \n`
118+ }
119+
40120async function compileJS ( file : string , config : CompileConfig = { } ) : Promise < [ Compiler , string ] > {
41- let { dir = path . dirname ( file ) , compiler, options = { } , output, strip = false } = config
42- const base = path . basename ( file , path . extname ( file ) )
43- const jsPath = output ?? path . join ( dir , `${ base } .js` )
44- await mkdir ( path . dirname ( jsPath ) , { recursive : true } )
45- await withOptions ( options , async ( ) => {
121+ let { dir = path . dirname ( file ) , compiler, options = { } , output, embed : inlineWasm = false , strip = false } = config
122+ const memcheck = options . memcheck ?? false
123+ const paths = buildPaths ( file , dir , output )
124+ await mkdir ( path . dirname ( paths . js ) , { recursive : true } )
125+ if ( ! inlineWasm ) await mkdir ( path . dirname ( paths . wasm ) , { recursive : true } )
126+ await withOptions ( { ...options , memcheck } , async ( ) => {
46127 compiler ??= await Compiler . create ( load )
47128 const em = await compiler . reload ( file )
129+ const exports : [ string , string ] [ ] = [ ]
130+ const runtime = await readFile ( libPath , 'utf8' )
131+ const mod = compiler . pipe . sources . module ( types . tag ( '' ) )
132+ for ( const [ i , [ name , fn ] ] of exportedFunctions ( compiler ) . entries ( ) ) {
133+ if ( ! isJSIdentifier ( name ) )
134+ throw new Error ( `Cannot export ${ JSON . stringify ( name ) } as a JS binding` )
135+ const tag = types . tag ( `__raven.lib.${ i } ` )
136+ const sig = lowerpattern ( ast . List ( ast . symbol ( 'args' ) ) )
137+ const method = mod . method ( tag , sig , libWrapperIR ( tag . path , fn ) )
138+ const fname = emitSig ( compiler , em , [ method , types . Ref ] , false )
139+ const wname = `raven.lib.${ name } `
140+ em . export ( fname , wname )
141+ exports . push ( [ name , wname ] )
142+ }
48143 const bytes = wasm . emitwasm ( em , strip )
49- const base64 = Buffer . from ( bytes ) . toString ( 'base64' )
50- const runtime = await readFile ( execPath , 'utf8' )
51- await writeFile ( jsPath , `${ runtime } \nbinary = Buffer.from('${ base64 } ', 'base64')\n` )
52- await chmod ( jsPath , 0o755 )
144+ if ( inlineWasm ) {
145+ const base64 = Buffer . from ( bytes ) . toString ( 'base64' )
146+ await writeFile ( paths . js , jsRuntime ( exports , runtime , { base64, memcheck } ) )
147+ } else {
148+ await writeFile ( paths . wasm , Buffer . from ( bytes ) )
149+ await writeFile ( paths . js , jsRuntime ( exports , runtime , { wasmFile : path . basename ( paths . wasm ) , memcheck } ) )
150+ }
151+ await chmod ( paths . js , 0o755 )
53152 } )
54- return [ compiler ! , jsPath ]
153+ return [ compiler ! , paths . js ]
55154}
56155
57156async function run ( cmd : string , args : readonly string [ ] = [ ] , options : SpawnOptions = { } ) {
@@ -62,6 +161,7 @@ async function run(cmd: string, args: readonly string[] = [], options: SpawnOpti
62161 } )
63162}
64163
164+ const libPath = path . join ( dirname , '../../dist/cli/lib.js' )
65165const execPath = path . join ( dirname , '../../dist/cli/exec.js' )
66166
67167async function exec ( file : string , args : string [ ] = [ ] , config ?: CompileConfig ) : Promise < void > {
0 commit comments