import { parse } from "./parser" ; import { emit } from "./emitter" ; import { encodeString , encodeNestedVector , encodeSection , op , unsignedLEB128 , VAL_TYPE , FUNCTION_TYPE , GLOBAL_TYPE , MUTABILITY , TYPE_IDX , EXPORT_TYPE , SECTION , MAGIC , WASM_VERSION , encodeFlatVector , } from "./encoding" ; import shims from "./shims" ; import * as Utils from "./utils" ; import { localFuncMap } from "./wasmFunctions" ; import { CompilerContext , TypedFunction , EelVersion } from "./types" ; import { WASM_MEMORY_SIZE } from "./constants" ; type CompilerOptions = { pools : { [ name ] : Set < string > ; } ; functions : { [ name ] : { pool : string ; code : string ; } ; } ; eelVersion ? : EelVersion ; preParsed ? : boolean ; } ; export function compileModule ( { pools , functions : funcs , eelVersion = 2 , preParsed = false , } ) { if ( Object . keys ( pools ) . includes ( "shims" ) ) { throw new Error ( 'You may not name a pool "shims". "shims" is reserved for injected JavaScript functions.' ) ; } const importedVars = [ ] ; Object . entries ( pools ) . forEach ( ( [ poolName , pool ] ) => { pool . forEach ( variableName => { importedVars . push ( [ poolName , variableName ] ) ; } ) ; } ) ; const varResolver = new Utils . ScopedIdMap ( ) ; importedVars . forEach ( ( [ poolName , variableName ] ) => { varResolver . get ( poolName , variableName ) ; } ) ; const functionImports = Object . entries ( shims ) . map ( ( [ name , func ] ) => { return { args : new Array ( func . length ) . fill ( null ) . map ( _ => VAL_TYPE . f64 ) , returns : [ VAL_TYPE . f64 ] , name , } ; } ) ; const localFuncOrder = [ ] ; const moduleFuncs = [ ] ; Object . entries ( funcs ) . forEach ( ( [ name , { pool , code } ] ) => { if ( pools [ pool ] == null ) { const poolsList = Object . keys ( pools ) ; if ( poolsList . length === 0 ) { throw new Error ( `The function "${ name }" was declared as using a variable ` + `pool named "${ pool }" but no pools were defined.` ) ; } throw new Error ( `The function "${ name }" was declared as using a variable ` + `pool named "${ pool }" which is not among the variable ` + `pools defined. The defined variable pools are: ` + `${ Utils . formatList ( poolsList ) }.` ) ; } const ast = preParsed ? code : parse ( code ) ; if ( typeof ast === "string" ) { throw new Error ( "Got passed unparsed code without setting the preParsed flag" ) ; } if ( ast . type !== "SCRIPT" ) { throw new Error ( "Invalid AST" ) ; } if ( ast . body . length === 0 ) { return ; } const localVariables = [ ] ; const context = { resolveVar : name => { if ( /^reg\d\d$/ . test ( name ) ) { return varResolver . get ( null , name ) ; } return varResolver . get ( pool , name ) ; } , resolveLocal : type => { localVariables . push ( type ) ; return localVariables . length - 1 ; } , resolveFunc : name => { const shimdex = functionImports . findIndex ( func => func . name === name ) ; if ( shimdex !== - 1 ) { const call = op . call ( shimdex ) ; if ( name === "rand" && eelVersion === 1 ) { return [ ... call , op . f64_floor ] ; } return call ; } if ( localFuncMap [ name ] == null ) { return null ; } let index = localFuncOrder . indexOf ( name ) ; if ( index === - 1 ) { localFuncOrder . push ( name ) ; index = localFuncOrder . length - 1 ; } return op . call ( index + functionImports . length ) ; } , rawSource : code , } ; const binary = emit ( ast , context ) ; moduleFuncs . push ( { binary , exportName : name , args : [ ] , returns : [ ] , localVariables , } ) ; } ) ; const localFuncs = localFuncOrder . map ( name => { const func = localFuncMap [ name ] ; if ( func == null ) { throw new Error ( `Undefined local function "${ name }"` ) ; } return func ; } ) ; const getSignatureKey = ( func ) => { return [ ... func . args , "|" , ... func . returns ] . join ( "-" ) ; } ; const types = [ ] ; const typeIndexByKey = new Map ( ) ; [ ... functionImports , ... localFuncs , ... moduleFuncs ] . forEach ( func => { const key = getSignatureKey ( func ) ; if ( typeIndexByKey . has ( key ) ) { return ; } types . push ( [ FUNCTION_TYPE , ... encodeFlatVector ( func . args ) , ... encodeFlatVector ( func . returns ) , ] ) ; typeIndexByKey . set ( key , types . length - 1 ) ; } ) ; function getTypeIndex ( func ) { const key = getSignatureKey ( func ) ; const typeIndex = typeIndexByKey . get ( key ) ; if ( typeIndex == null ) { throw new Error ( `Failed to get a type index for key ${ key }` ) ; } return typeIndex ; } const imports = [ ... importedVars . map ( ( [ namespace , name ] ) => { return [ ... encodeString ( namespace ) , ... encodeString ( name ) , ... [ GLOBAL_TYPE , VAL_TYPE . f64 , MUTABILITY . var ] , ] ; } ) , ... functionImports . map ( ( func , i ) => { const typeIndex = getTypeIndex ( func ) ; return [ ... encodeString ( "shims" ) , ... encodeString ( func . name ) , ... [ TYPE_IDX , ... unsignedLEB128 ( typeIndex ) ] , ] ; } ) , ] ; const functions = [ ... localFuncs , ... moduleFuncs ] . map ( func => { const typeIndex = getTypeIndex ( func ) ; return unsignedLEB128 ( typeIndex ) ; } ) ; const memories = [ [ 0x01 , ... unsignedLEB128 ( WASM_MEMORY_SIZE ) , ... unsignedLEB128 ( WASM_MEMORY_SIZE ) , ] , ] ; const globalCount = varResolver . size ( ) - importedVars . length ; const globals = Utils . times ( globalCount , ( ) => { return [ VAL_TYPE . f64 , MUTABILITY . var , ... op . f64_const ( 0 ) , op . end , ] ; } ) ; const xports = [ ... moduleFuncs ] . map ( ( func , i ) => { const funcIndex = i + functionImports . length + localFuncs . length ; return [ ... encodeString ( func . exportName ) , EXPORT_TYPE . FUNC , ... unsignedLEB128 ( funcIndex ) , ] ; } ) ; const codes = [ ... localFuncs , ... moduleFuncs ] . map ( func => { const localTypes = ( func . localVariables ?? [ ] ) . map ( type => { return [ ... unsignedLEB128 ( 1 ) , type ] ; } ) ; return encodeFlatVector ( [ ... encodeNestedVector ( localTypes ) , ... func . binary , op . end , ] ) ; } ) ; return new Uint8Array ( [ ... MAGIC , ... WASM_VERSION , ... encodeSection ( SECTION . TYPE , types ) , ... encodeSection ( SECTION . IMPORT , imports ) , ... encodeSection ( SECTION . FUNC , functions ) , ... encodeSection ( SECTION . MEMORY , memories ) , ... encodeSection ( SECTION . GLOBAL , globals ) , ... encodeSection ( SECTION . EXPORT , xports ) , ... encodeSection ( SECTION . CODE , codes ) , ] ) ; }