I am trying to build a game with C and WASM, and I am running into a very nondescript error.
Result of running make -s
:
error: Linking globals named 'absv': symbol multiply defined!
(absv
is my custom abs()
function (I renamed it from abs
to absv
in an attempt to fix the problem), because I can't use the standard library)
I would appreciate any help, because I have been at this for way too long.
This might be a lot...
Makefile:
all: main
CC = clang
LLCC = llc
LINK = llvm-link
WASMLD = wasm-ld
override CLINKFLAGS += -nostdinc -nostdlib -fno-stack-protector -O3 --target=wasm32 -emit-llvm -c -S -Wno-pragma-once-outside-header
override LLCCFLAGS += -exception-model=wasm -march=wasm32 -filetype=obj
override LINKFLAGS += -S -v -o out.ll *.ll
override WASMLDFLAGS += --no-entry --export-all --allow-undefined --lto-O3 -O3 -z stack-size=8192
SRCS = $(shell find . -name '.ccls-cache' -type d -prune -o -type f -name '*.c' -print)
main: $(SRCS)
rm -rf *.ll
$(CC) $(CLINKFLAGS) $(SRCS)
$(LINK) $(LINKFLAGS)
$(LLCC) $(LLCCFLAGS) out.ll
$(WASMLD) $(WASMLDFLAGS) -o out.wasm out.o
clean:
rm -rf *.o *.s *.ll out.wasm
script.js:
(async () => {
let buffer = [];
const game = document.getElementById("game");
const gamectx = game.getContext("2d");
const width = window.innerWidth;
const height = window.innerHeight;
const screen = game.getContext("2d").getImageData(0, 0, game.width, game.height);
const assembly = await WebAssembly.instantiateStreaming(fetch("./out.wasm"), {
env: {
init: (none) => {},
getWidth: (none) => {
return game.width;
},
getHeight: (none) => {
return game.height;
},
drawPixelToBuffer: (x, y, color) => {
buffer.push([(color & 0b000000000000000011111111), (color >> 8) & 0b000000000000000011111111, (color >> 16) & 0b000000000000000011111111]);
},
drawFrame: (none) => {
for (let i = 0; i < buffer.length; i++) {
screen.data[i * 4] = buffer[i][0];
screen.data[i * 4 + 1] = buffer[i][2];
screen.data[i * 4 + 2] = buffer[i][3];
screen.data[i * 4 + 3] = 255;
}
gamectx.putImageData(screen, 0, 0);
buffer = [];
}
}
});
const c = assembly.instance.exports;
c.start();
})();
main.c:
#include "types.h"
#include "js.h"
#include "render2d.c"
void start() {
init();
for (i32 y = 0; y < getHeight(); y++) {
for (i32 x = 0; x < getWidth(); x++) {
drawPixelToBuffer((color) {0, 0, 0});
}
}
drawFrame();
return;
}
types.h:
#pragma once
// Primatives
typedef unsigned char b;
typedef char i8;
typedef short int i16;
typedef int i32;
typedef long int i64;
typedef unsigned char u8;
typedef unsigned short int u16;
typedef unsigned int u32;
typedef unsigned long int u64;
typedef float f32;
typedef double f64;
// Customs
typedef struct {
u8 red;
u8 green;
u8 blue;
} color;
typedef color texture[64][64];
typedef struct {
u16 x;
u16 y;
} pt2;
typedef struct {
f64 x;
f64 y;
f64 z;
} pt3;
typedef struct {
pt2 p1;
pt2 p2;
pt3 p3;
texture texture;
} tri2;
typedef struct {
pt3 p1;
pt3 p2;
pt3 p3;
texture texture;
} tri3;
js.h:
#pragma once
#include "types.h"
extern void init();
extern i32 getWidth();
extern i32 getHeight();
extern void drawPixelToBuffer(color color);
extern void drawFrame();
render2d.c:
#pragma once
#include "types.h"
#include "js.h"
#include "math/sqrt.c"
#include "math/expn.c"
#include "math/absv.c"
void drawTri2(tri2 tri) {
// Get all points on a line
pt2 line1[(i32) sqrt(expn(absv(tri.p1.x - tri.p2.x), 2) + expn(absv(tri.p1.y - tri.p2.y), 2))];
pt2 line2[(i32) sqrt(expn(absv(tri.p2.x - tri.p3.x), 2) + expn(absv(tri.p2.y - tri.p3.y), 2))];
pt2 line3[(i32) sqrt(expn(absv(tri.p1.x - tri.p3.x), 2) + expn(absv(tri.p1.y - tri.p3.y), 2))];
}
math/absv.c:
#pragma once
#include "../types.h"
f64 absv(f64 n) {
return n < 0 ? -n : n;
}
math/eexp.c:
#pragma once
#include "../types.h"
#include "absv.c"
f64 eexp(f64 n) {
f64 a = 1.0;
f64 o = 0;
for (i32 i = 1; o != o + a ; i++) {
o += a;
a = a * absv(n) / i;
}
return n < 0 ? 1 / o : o;
}
math/expn.c:
#pragma once
#include "../types.h"
#include "nthroot.c"
f64 expn(f64 n, f64 p) {
return nthroot(p, n);
}
math/nlog.c:
#pragma once
#include "../types.h"
f64 nlog(f64 n) {
f64 oldSum = 0.0;
f64 denom = 1.0;
f64 frac = (n - 1) / (n + 1);
f64 term = frac;
f64 sum = term;
while (sum != oldSum) {
oldSum = sum;
denom += 2.0;
frac *= ((n - 1) / (n + 1)) * ((n - 1) / (n + 1));
sum += frac / denom;
}
return sum * 2.0;
}
math/nthroot.c:
#pragma once
#include "../types.h"
#include "eexp.c"
#include "nlog.c"
f64 nthroot(f64 r, f64 n) {
return eexp(nlog(n) / r);
}
math/sqrt.c:
#pragma once
#include "../types.h"
#include "nthroot.c"
f64 sqrt(f64 n) {
return nthroot(2.0, n);
}
Retired Ninja had it right: Don't do #include "absv.c"
You do that in two .c
files:
render2d.c
math/eexp.c
They both define absv
so you get the "multiply defined" error.
Don't have any .c
file include other .c
files. You've already tried to fix this with [numerous] #pragma once
. Get rid of them.
What you want is a .h
file that defines prototypes for functions. You already do this in js.h
So, as an example, create math/math.h
:
#include "../types.h"
f64 absv(f64 n);
f64 eexp(f64 n);
f64 expn(f64 n, f64 p);
f64 nlog(f64 n);
f64 nthroot(f64 r, f64 n);
f64 sqrt(f64 n);
Then, every .c
file should do just:
#include "math/math.h"
instead of the various:
#include "math/sqrt.c"
Of course, now you have to compile each .c
file separately (e.g. in the math
directory):
cc -c sqrt.c
cc -c absv.c
...
Then, you link together the various .o
files you've created:
cc -o render2d render2d.o math/sqrt.o math/absv.o ...
I may not have got the directory hierarchies strictly right. Adjust to suit ;-)