diff options
author | Alon Zakai <alonzakai@gmail.com> | 2012-02-18 19:25:44 -0800 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2012-02-18 19:25:44 -0800 |
commit | ef37c712f2325a60ebca5162a5cb4bef9ef3292c (patch) | |
tree | 0dd525109fcfc2f23ec66ee87d3dbf25699de38c | |
parent | 2e5522205545f910bcdf2e12c7cd7c7eca9a06bf (diff) |
ccall
-rw-r--r-- | src/preamble.js | 61 | ||||
-rwxr-xr-x | tests/runner.py | 63 |
2 files changed, 122 insertions, 2 deletions
diff --git a/src/preamble.js b/src/preamble.js index c476118c..1d086a93 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -357,13 +357,71 @@ function assert(condition, text) { } } +var globalScope = this; + +// C calling interface. A convenient way to call C functions (in C files, or +// defined with extern "C"). +// +// Note: LLVM optimizations can inline and remove functions, after which you will not be +// able to call them. Adding +// +// __attribute__((used)) +// +// to the function definition will prevent that. +// +// Note: Closure optimizations will minify function names, making +// functions no longer callable. If you run closure (on by default +// in -O2 and above), you should export the functions you will call +// by calling emcc with something like +// +// -s EXPORTED_FUNCTIONS='["_func1","_func2"]' +// +// @param ident The name of the C function +// @param returnType The return type of the function, one of the JS types 'number' or 'string', or 'pointer' for any type of C pointer. +// @param argTypes An array of the types of arguments for the function (if there are no arguments, this can be ommitted). Types are as in returnType. +// @param args An array of the arguments to the function, as native JS values (except for 'pointer', which is a 'number'). +// Note that string arguments will be stored on the stack (the JS string will become a C string on the stack). +// @return The return value, as a native JS value (except for 'pointer', which is a 'number'). +function ccall(ident, returnType, argTypes, args) { + function toC(value, type) { + if (type == 'string') { + var ret = STACKTOP; + Runtime.stackAlloc(value.length+1); + writeStringToMemory(value, ret); + return ret; + } + return value; + } + function fromC(value, type) { + if (type == 'string') { + return Pointer_stringify(value); + } + return value; + } + try { + var func = eval('_' + ident); + } catch(e) { + try { + func = globalScope['Module']['_' + ident]; // closure exported function + } catch(e) {} + } + assert(func, 'Cannot call unknown function ' + ident + ' (perhaps LLVM optimizations or closure removed it?)'); + var i = 0; + var cArgs = args ? args.map(function(arg) { + return toC(arg, argTypes[i++]); + }) : []; + return fromC(func.apply(null, cArgs), returnType); +} +Module["ccall"] = ccall; + // Sets a value in memory in a dynamic way at run-time. Uses the // type data. This is the same as makeSetValue, except that // makeSetValue is done at compile-time and generates the needed // code then, whereas this function picks the right code at // run-time. // Note that setValue and getValue only do *aligned* writes and reads! - +// Note that ccall uses JS types as for defining types, while setValue and +// getValue need LLVM types ('i8', 'i32') - this is a lower-level operation function setValue(ptr, value, type, noSafe) { type = type || 'i8'; if (type[type.length-1] === '*') type = 'i32'; // pointers are 32-bit @@ -398,7 +456,6 @@ function setValue(ptr, value, type, noSafe) { Module['setValue'] = setValue; // Parallel to setValue. - function getValue(ptr, type, noSafe) { type = type || 'i8'; if (type[type.length-1] === '*') type = 'i32'; // pointers are 32-bit diff --git a/tests/runner.py b/tests/runner.py index f55975ca..7154756c 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -4894,6 +4894,69 @@ Block 0: ''', post_build=post1) ### Integration tests + def test_ccall(self): + if self.emcc_args is not None and '-O2' in self.emcc_args: + self.emcc_args += ['--closure', '1'] # Use closure here, to test we export things right + + src = r''' + #include <stdio.h> + + // Optimizations might wipe out our functions without this + #define KEEPALIVE __attribute__((used)) + + extern "C" { + int KEEPALIVE get_int() { return 5; } + float KEEPALIVE get_float() { return 3.14; } + char * KEEPALIVE get_string() { return "hello world"; } + void KEEPALIVE print_int(int x) { printf("%d\n", x); } + void KEEPALIVE print_float(float x) { printf("%.2f\n", x); } + void KEEPALIVE print_string(char *x) { printf("%s\n", x); } + int KEEPALIVE multi(int x, float y, int z, char *str) { puts(str); return (x+y)*z; } + int * KEEPALIVE pointer(int *in) { printf("%d\n", *in); static int ret = 21; return &ret; } + } + + int main(int argc, char **argv) { + // keep them alive + if (argc == 10) return get_int(); + if (argc == 11) return get_float(); + if (argc == 12) return get_string()[0]; + if (argc == 13) print_int(argv[0][0]); + if (argc == 14) print_float(argv[0][0]); + if (argc == 15) print_string(argv[0]); + if (argc == 16) pointer((int*)argv[0]); + if (argc % 17 == 12) return multi(argc, float(argc)/2, argc+1, argv[0]); + return 0; + } + ''' + + post = ''' +def process(filename): + src = \'\'\' + var Module = { + 'postRun': function() { + print('*'); + var ret; + ret = ccall('get_int', 'number'); print([typeof ret, ret]); + ret = ccall('get_float', 'number'); print([typeof ret, ret.toFixed(2)]); + ret = ccall('get_string', 'string'); print([typeof ret, ret]); + ret = ccall('print_int', null, ['number'], [12]); print(typeof ret); + ret = ccall('print_float', null, ['number'], [14.56]); print(typeof ret); + ret = ccall('print_string', null, ['string'], ["cheez"]); print(typeof ret); + ret = ccall('multi', 'number', ['number', 'number', 'number', 'string'], [2, 1.4, 3, 'more']); print([typeof ret, ret]); + var p = ccall('malloc', 'pointer', ['number'], [4]); + setValue(p, 650, 'i32'); + ret = ccall('pointer', 'pointer', ['pointer'], [p]); print([typeof ret, getValue(ret, 'i32')]); + print('*'); + } + }; + \'\'\' + open(filename, 'r').read() + open(filename, 'w').write(src) +''' + + Settings.EXPORTED_FUNCTIONS = ['_get_int', '_get_float', '_get_string', '_print_int', '_print_float', '_print_string', '_multi', '_pointer', '_malloc'] + + self.do_run(src, '*\nnumber,5\nnumber,3.14\nstring,hello world\n12\nundefined\n14.56\nundefined\ncheez\nundefined\nmore\nnumber,10\n650\nnumber,21\n*\n', post_build=post) + def test_scriptaclass(self): header_filename = os.path.join(self.get_dir(), 'header.h') header = ''' |