From 072f8c354eac91c000e0d35a3ef965a9f30de21c Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 20 Dec 2019 14:12:32 +0100 Subject: [PATCH] interp: add runtime fallback for mapassign operations Some mapassign operations cannot (yet) be done by the interp package. Implement a fallback mechanism so that these operations can still be performed at runtime. --- interp/frame.go | 29 +++++++++++++++++++++++++-- interp/testdata/map.ll | 40 ++++++++++++++++++++++++++++++++++++++ interp/testdata/map.out.ll | 13 +++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/interp/frame.go b/interp/frame.go index d80587d5..cf429969 100644 --- a/interp/frame.go +++ b/interp/frame.go @@ -283,7 +283,20 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re valPtr := fr.getLocal(inst.Operand(3)).(*LocalValue) m, ok := fr.getLocal(inst.Operand(0)).(*MapValue) if !ok || !keyBuf.IsConstant() || !keyLen.IsConstant() || !valPtr.IsConstant() { - return nil, nil, fr.errorAt(inst, "could not update map with string key") + // The mapassign operation could not be done at compile + // time. Do it at runtime instead. + m := fr.getLocal(inst.Operand(0)).Value() + fr.markDirty(m) + llvmParams := []llvm.Value{ + m, // *runtime.hashmap + fr.getLocal(inst.Operand(1)).Value(), // key.ptr + fr.getLocal(inst.Operand(2)).Value(), // key.len + fr.getLocal(inst.Operand(3)).Value(), // value (unsafe.Pointer) + fr.getLocal(inst.Operand(4)).Value(), // context + fr.getLocal(inst.Operand(5)).Value(), // parentHandle + } + fr.builder.CreateCall(callee, llvmParams, "") + continue } // "key" is a Go string value, which in the TinyGo calling convention is split up // into separate pointer and length parameters. @@ -294,7 +307,19 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re valPtr := fr.getLocal(inst.Operand(2)).(*LocalValue) m, ok := fr.getLocal(inst.Operand(0)).(*MapValue) if !ok || !keyBuf.IsConstant() || !valPtr.IsConstant() { - return nil, nil, fr.errorAt(inst, "could not update map") + // The mapassign operation could not be done at compile + // time. Do it at runtime instead. + m := fr.getLocal(inst.Operand(0)).Value() + fr.markDirty(m) + llvmParams := []llvm.Value{ + m, // *runtime.hashmap + fr.getLocal(inst.Operand(1)).Value(), // key + fr.getLocal(inst.Operand(2)).Value(), // value + fr.getLocal(inst.Operand(3)).Value(), // context + fr.getLocal(inst.Operand(4)).Value(), // parentHandle + } + fr.builder.CreateCall(callee, llvmParams, "") + continue } m.PutBinary(keyBuf, valPtr) case callee.Name() == "runtime.stringConcat": diff --git a/interp/testdata/map.ll b/interp/testdata/map.ll index 05d9e916..50f95036 100644 --- a/interp/testdata/map.ll +++ b/interp/testdata/map.ll @@ -5,10 +5,13 @@ target triple = "armv6m-none-eabi" %runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 } @main.m = global %runtime.hashmap* null +@main.binaryMap = global %runtime.hashmap* null +@main.stringMap = global %runtime.hashmap* null @main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT" declare %runtime.hashmap* @runtime.hashmapMake(i8, i8, i32, i8* %context, i8* %parentHandle) declare void @runtime.hashmapBinarySet(%runtime.hashmap*, i8*, i8*, i8* %context, i8* %parentHandle) +declare void @runtime.hashmapStringSet(%runtime.hashmap*, i8*, i32, i8*, i8* %context, i8* %parentHandle) declare void @llvm.lifetime.end.p0i8(i64, i8*) declare void @llvm.lifetime.start.p0i8(i64, i8*) @@ -20,6 +23,7 @@ entry: define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { entry: +; Test that hashmap optimizations generally work (even with lifetimes). %hashmap.key = alloca i8 %hashmap.value = alloca %runtime._string %0 = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 8, i32 1, i8* undef, i8* null) @@ -32,5 +36,41 @@ entry: call void @llvm.lifetime.end.p0i8(i64 1, i8* %hashmap.key) call void @llvm.lifetime.end.p0i8(i64 8, i8* %hashmap.value.bitcast) store %runtime.hashmap* %0, %runtime.hashmap** @main.m + + ; Other tests, that can be done in a separate function. + call void @main.testNonConstantBinarySet() + call void @main.testNonConstantStringSet() + ret void +} + +; Test that a map loaded from a global can still be used for mapassign +; operations (with binary keys). +define internal void @main.testNonConstantBinarySet() { + %hashmap.key = alloca i8 + %hashmap.value = alloca i8 + ; Create hashmap from global. This breaks the normal hashmapBinarySet + ; optimization, to test the fallback. + %map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 1, i32 1, i8* undef, i8* null) + store %runtime.hashmap* %map.new, %runtime.hashmap** @main.binaryMap + %map = load %runtime.hashmap*, %runtime.hashmap** @main.binaryMap + ; Do the binary set to the newly loaded map. + store i8 1, i8* %hashmap.key + store i8 2, i8* %hashmap.value + call void @runtime.hashmapBinarySet(%runtime.hashmap* %map, i8* %hashmap.key, i8* %hashmap.value, i8* undef, i8* null) + ret void +} + +; Test that a map loaded from a global can still be used for mapassign +; operations (with string keys). +define internal void @main.testNonConstantStringSet() { + %hashmap.value = alloca i8 + ; Create hashmap from global. This breaks the normal hashmapStringSet + ; optimization, to test the fallback. + %map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 8, i8 1, i32 1, i8* undef, i8* null) + store %runtime.hashmap* %map.new, %runtime.hashmap** @main.stringMap + %map = load %runtime.hashmap*, %runtime.hashmap** @main.stringMap + ; Do the string set to the newly loaded map. + store i8 2, i8* %hashmap.value + call void @runtime.hashmapStringSet(%runtime.hashmap* %map, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7, i8* %hashmap.value, i8* undef, i8* null) ret void } diff --git a/interp/testdata/map.out.ll b/interp/testdata/map.out.ll index 7806226b..e891362b 100644 --- a/interp/testdata/map.out.ll +++ b/interp/testdata/map.out.ll @@ -5,11 +5,24 @@ target triple = "armv6m-none-eabi" %runtime._string = type { i8*, i32 } @main.m = local_unnamed_addr global %runtime.hashmap* @"main$map" +@main.binaryMap = local_unnamed_addr global %runtime.hashmap* @"main$map.4" +@main.stringMap = local_unnamed_addr global %runtime.hashmap* @"main$map.6" @main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT" @"main$mapbucket" = internal unnamed_addr global { [8 x i8], i8*, [8 x i8], [8 x %runtime._string] } { [8 x i8] c"\04\00\00\00\00\00\00\00", i8* null, [8 x i8] c"\01\00\00\00\00\00\00\00", [8 x %runtime._string] [%runtime._string { i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7 }, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer] } @"main$map" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, [8 x i8], [8 x %runtime._string] }, { [8 x i8], i8*, [8 x i8], [8 x %runtime._string] }* @"main$mapbucket", i32 0, i32 0, i32 0), i32 1, i8 1, i8 8, i8 0 } +@"main$alloca.2" = internal global i8 1 +@"main$alloca.3" = internal global i8 2 +@"main$map.4" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* null, i32 0, i8 1, i8 1, i8 0 } +@"main$alloca.5" = internal global i8 2 +@"main$map.6" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* null, i32 0, i8 8, i8 1, i8 0 } + +declare void @runtime.hashmapBinarySet(%runtime.hashmap*, i8*, i8*, i8*, i8*) local_unnamed_addr + +declare void @runtime.hashmapStringSet(%runtime.hashmap*, i8*, i32, i8*, i8*, i8*) local_unnamed_addr define void @runtime.initAll() unnamed_addr { entry: + call void @runtime.hashmapBinarySet(%runtime.hashmap* @"main$map.4", i8* @"main$alloca.2", i8* @"main$alloca.3", i8* undef, i8* null) + call void @runtime.hashmapStringSet(%runtime.hashmap* @"main$map.6", i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7, i8* @"main$alloca.5", i8* undef, i8* null) ret void }